当前位置: 首页 > 科技观察

基于Python的Scrapy爬虫入门:代码详解

时间:2023-03-18 11:17:12 科技观察

一、内容分析接下来创建一个爬虫项目,以土虫网为例,抓取里面的图片。在顶部菜单“Discovery”中“Tags”是各种图片的分类,点击一个标签,比如“美女”,网页的链接是:https://tuchong.com/tags/beauty/,我们用这个作为爬虫入口,分析页面:打开页面后,会有一张一张的图集,点击图集可以全屏浏览图片,向下滚动页面,会出现更多的图集,没有页码环境。在Chrome上右击选择“InspectElement”打开开发者工具,查看页面源代码。内容如下:

即没有实际图集内容,所以可以断定该页面使用了Ajax请求。只有当浏览器加载页面时,才会请求图集内容并将其添加到div.widget-gallery。通过开发者工具查看XHR请求地址:https://tuchong。com/rest/tags/beauty/posts?page=1&count=20&order=weekly&before_timestamp=参数很简单,page是页码,count是每页的图片数,order是排序,before_timestamp为空,因为Tuworm是一个推送内容类型的网站,所以before_timestamp应该是一个时间值。不同的时间会显示不同的内容。这里我们舍弃掉,不管时间如何,直接从最新的页面抓取。请求结果为JSON格式,降低爬取难度。结果如下:{"postList":[{"post_id":"15624611","type":"multi-photo","url":"https://weishexi.tuchong.com/15624611/","site_id":"443122","author_id":"443122","published_at":"2017-10-2818:01:03","excerpt":"October18Day","favorites":4052,"comments":353,"rewardable":true,"parent_comments":"165","re??wards":"2","views":52709,"title":"微风不入秋,恰到好处","image_count":15,"图片":[{"img_id":11585752,"user_id":443122,"标??题":"","摘录":"","宽度":5016,"高度":3840},{"img_id":11585737,"user_id":443122,"标??题":"","摘录":"","宽度":3840,"height":5760},...],"title_image":null,"tags":[{"tag_id":131,"type":"subject","tag_name":"人物形象","event_type":"","vote":""},{"tag_id":564,"type":"subject","tag_name":"美女","event_type":"","vote":""}],"favorite_list_prefix":[],"reward_list_prefix":[],"comment_list_prefix":[],"cover_image_src":"https://photo.tuchong.com/443122/g/11585752.webp","is_favorite":false}],"siteList":{...},"following":false,"coverUrl":"https://photo.tuchong.com/443122/ft640/11585752.webp","tag_name":"美女","tag_id":"564","url":"https://tuchong.com/tags/%E7%BE%8E%E5%A5%B3/","more":true,"result":"SUCCESS"}根据属性名很容易知道对应内容的含义,这里我们只需要关心postlist属性,它对应的一个数组元素就是一个atlas,atlas元素中有几个属性,我们需要用到:url:单个图集的页面地址post_id:图集编号,在网站内要唯一,可以用来判断内容是否被抓取site_id:作者站号,title:标题摘录:用于构建图源链接摘要文字类型:图集类型,目前发现有两种,一种是multi-photo,就是纯图片,一种是text,就是文字图片混合的文章式页面。两种内容结构不同,需要不同的爬取方式,本例中只抓取纯照片类型,文本类型直接丢弃tags:atlas标签,有多个,是一个对象数组,每个对象包含一个img_id属性,需要用到根据图片浏览页面分析,基本上图片的地址都是这样的格式:https://photo.tuchong.com/{site_id}/f/{img_id}.jpg,通过以上信息很容易合成2.创建项目,输入cmder命令运行工具,输入workonscrapy进入之前创建的虚拟环境,出现(Scrapy)标识此时会出现在命令提示符前,标志是在虚拟环境中,将相关路径添加到PATH环境变量中,以供开发使用。输入scrapystartprojecttuchong创建项目tuchong进入项目主目录,输入scrapygenspiderphototuchong.com创建一个名为photo(与项目不重名)的爬虫,爬取tuchong.com域名(这个需要修改,先在这里输入一个大概的地址),一个项目可以包含多个爬虫。经过以上步骤,项目会自动创建一些文件和设置。目录结构如下:(PROJECT)│scrapy.cfg│└─tuchong│items.py│middlewares.py│pipelines。py│settings.py│__init__.py│├─spiders││photo.py││__init__.py││└─__pycache__│__init__.cpython-36.pyc│└─__pycache__settings.cpython-36.pyc__init__.cpython-36.pycscrapy.cfg:基本设置items.py:爬取item的结构定义middlewares.py:中间件定义,无需改动本例中pipelines.py:pipeline定义,用于抓取数据后处理settings.py:全局设置spiders\photo.py:爬虫的主体,定义了如何抓取需要的数据3.在主代码items.py中创建一个TuchongItem类,定义需要的属性。该属性继承自scrapy.Field。该值可以是字符、数字或列表或字典等:url=scrapy.Field()image_count=scrapy.Field()images=scrapy.Field()tags=scrapy.Field()excerpt=scrapy.Field()...这些属性的值会在履带体。spiders\photo.py该文件由命令scrapygenspiderphototuchong.com自动创建,里面初始内容如下:importscrapyclassPhotoSpider(scrapy.Spider):name='photo'allowed_domains=['tuchong.com']start_urls=['http://tuchong.com/']defparse(self,response):传递爬虫名称,允许域名allowed_domains(如果链接不属于这个域名会被丢弃,允许多个),起始地址start_urls将从这里定义地址捕获(允许多个)函数parse是处理请求内容的默认回调函数。参数response为请求内容,页面内容文本存放在response.body中。我们需要稍微修改默认代码来满足多个页面。循环发送请求,需要重载start_requests函数,通过循环语句构造多页链接请求。修改后的代码如下:importscrapy,jsonfrom..itemsimportTuchongItemclassPhotoSpider(scrapy.Spider):name='photo'#allowed_domains=['tuchong.com']#start_urls=['http://tuchong.com/']defstart_requests(self):url='https://tuchong.com/rest/tags/%s/posts?page=%d&count=20&order=每周';#抓取10页,每页20张图集#指定parse为回调函数,返回Requests请求对象forpageinrange(1,11):yieldscrapy.Request(url=url%('beauty',page),callback=self.parse)#回调函数,处理抓取内容填充TuchongItem属性defparse(self,response):body=json.loads(response.body_as_unicode())items=[]forpostinbody['postList']:item=TuchongItem()item['type']=post['type']item['post_id']=post['post_id']item['site_id']=post['site_id']item['title']=post['title']item['url']=post['url']item['excerpt']=post['excerpt']item['image_count']=int(post['image_count'])item['images']={}#processimagesinto{img_id:img_url}objectarrayforimginpost.get('images',''):img_id=img['img_id']url='https://photo.tuchong.com/%s/f/%s.jpg'%(item['site_id'],img_id)item['images'][img_id]=urlitem['tags']=[]#流程tagsintotag_namearrayfortaginpost.get('tags',''):item['tags'].append(tag['tag_name'])items.append(item)returnitems经过这几步,抓取到的数据就是保存在TuchongItem类中,便于处理和保存为结构化数据如前所述,并非所有捕获的项目都是必需的。比如这个例子,我们只需要type=”multi_photo类型的图集,图片太少了。这些捕获的item的过滤操作以及如何保存需要在pipelines.py中进行处理,该文件中已经默认创建了类TuchongPipeline并重载了process_item函数。通过修改这个函数,只返回那些符合条件的项目。代码如下:importscrapy,jsonfrom..itemsimportTuchongItemclassPhotoSpider(scrapy.Spider):name='photo'#allowed_domains=['tuchong.com']#start_urls=['http://tuchong.com/']defstart_requests(self):url='https://tuchong.com/rest/tags/%s/posts?page=%d&count=20&order=weekly';#抓取10页,每页20张图集#指定parse为回调函数和returntheRequests请求对象forpageinrange(1,11):yieldscrapy.Request(url=url%('Beauty',page),callback=self.parse)#回调函数,处理抓取的内容填充TuchongItem属性defparse(self,响应):body=json.loads(response.body_as_unicode())items=[]forpostinbody['postList']:item=TuchongItem()item['type']=post['type']item['post_id']=post['post_id']item['site_id']=post['site_id'']item['title']=post['title']item['url']=post['url']item['excerpt']=post['excerpt']item['image_count']=int(post['image_count'])item['images']={}#processimagesinto{img_id:img_url}对象数组forimginpost.get('images',''):img_id=img['img_id']url='https://photo.tuchong.com/%s/f/%s.jpg'%(item['site_id'],img_id)item['images'][img_id]=urlitem['tags']=[]#将标签处理成tag_name数组fortaginpost.get('tags',''):item['tags'].append(tag['tag_name'])items.append(item)returnitems当然在parse中不使用pipeline直接处理也是一样的,只是这样结构更清晰,功能更多可以使用FilePipelines和ImagePipelines,每抓取一个item都会触发process_item,可以重载open_spider和close_spider函数来处理爬虫打开和关闭时的动作。注意:管道需要在项目中注册才能使用。在settings.py中添加:ITEM_PIPELINES={'tuchong.pipelines.TuchongPipeline':300,#Pipeline名称:运行优先级(小数优先)}另外大部分网站都有反爬虫robots.txt排除协议,设置ROBOTSTXT_OBEY=真要无视这些约定,不错,这好像是君子约定。如果网站设置了浏览器UserAgent或者IP地址检测来防爬虫,那么就需要更高级的Scrapy功能,本文不再赘述。4、运行并返回cmder命令行进入项目目录,输入命令:scrapycrawlphoto终端会输出所有的爬取结果和调试信息,并列出***中爬虫运行的统计信息,例如:[scrapy.crawl.photo]。statscollectors]INFO:DumpingScrapystats:{'downloader/request_bytes':491,'downloader/request_count':2,'downloader/request_method_count/GET':2,'downloader/response_bytes':10224,'downloader/response_count':2,'下载器/response_status_count/200':2,'finish_reason':'finished','finish_time':datetime.datetime(2017,11,27,7,20,24,414201),'item_dropped_count':5,'item_dropped_reasons_count/DropItem':5,'item_scraped_count':15,'log_count/DEBUG':18,'log_count/INFO':8,'log_count/WARNING':5,'response_received_count':2,'scheduler/dequeued':1,'scheduler/dequeued/memory':1,'scheduler/enqueued':1,'scheduler/enqueued/memory':1,'start_time':datetime.datetime(2017,11,27,7,20,23,867300)}主要关注ERROR和WARNING,这里的Warning实际上是未满足的DropItem异常。5、保存结果大多数情况下,保存爬取的结果是很有必要的。默认情况下,可以将item.py中定义的属性保存到文件中。只需要在命令行加上参数-o{filename}即可:scrapycrawlphoto-ooutput.json#输出的是JSON文件scrapycrawlphoto-ooutput.csv#输出的是CSV文件注意:输出到文件的items是没有经过图冲Pipeline过滤的item,只要parse函数中返回的Item都会被输出,所以也可以在parse中使用,如果需要保存到数据库,则需要增加额外的代码处理,例如,可以在pipelines.py中的process_item后添加:...defprocess_item(self,item,spider):...else:print(item['url'])self.myblog.add_post(item)#myblog是一个数据库处理数据库操作的类returnitem...为了排除插入数据库操作中的重复内容,可以使用item['post_id']进行判断,如果存在则跳过。