作者:淡月如水夜宿雨 | 来源:互联网 | 2023-08-25 16:37
爬取豆瓣Top250电影为了寻找练手的项目,搜索了无数文档,自己总结了一套关于scrapy写spider的“标准”模板,稍后奉上。在这无数文档中,不知道是出于什么原因,要我说至少有
爬取豆瓣Top250电影
为了寻找练手的项目,搜索了无数文档,自己总结了一套关于scrapy写spider的“标准”模板,稍后奉上。在这无数文档中,不知道是出于什么原因,要我说至少有一半提到了爬取豆瓣top250电影,那我也只能先爬为敬了。
如果你看懂了我上一篇文章,那么爬豆瓣就很简单了,废话不多说,我直接上代码:
import scrapy
class doubantop250(scrapy.Spider):
name = "doubantop250"
headler = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Mobile Safari/537.36',
#'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
}
start_urls = [
'https://movie.douban.com/top250'
]
def start_requests(self):
for url in self.start_urls:
yield scrapy.Request(url=url, callback=self.parse, headers=self.headler)
def parse(self, response):
for lists in response.css('ol.grid_view li div.item'):
yield {
"名次": lists.css('div.pic em::text').extract(),
"信息": lists.css('div.info div.bd p::text').extract(),
"电影名": lists.css('div.info div.hd a span.title::text').extract(),
"评分":lists.css('div.info div.bd div.star span.rating_num::text').extract(),
"引言": lists.css('div.info div.bd p.quote span.inq::text').extract()
}
next_page = response.css('div.paginator span.next a::attr(href)').extract_first()
if next_page is not None:
next_page = response.urljoin(next_page)
yield scrapy.Request(next_page, callback=self.parse, headers=self.headler)
同样的css表达式scrapy shell抓不到东西?
这是因为shell里生成的request默认是没有带User-agent字段的,这个字段在反爬技术中很重要,怎么解决呢?也很简单,给它一个User-agent呀。在shell里输入:
>>> header = {
... 'User-Agent': 'Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Mobile Safari/537.36',
... }
>>> from scrapy import Request
>>> req = scrapy.Request(url='https://movie.douban.com/top250', headers=header)
>>> fetch(req)
爬取灌篮高手漫画全集
这是我发现的另一个觉得很有帮助的例子:Python3网络爬虫(十二):初识Scrapy之再续火影情缘,作者爬取了一个盗版漫画网站上的火影漫画。我觉得这篇文章最可取之处是教我们系统化的使用Scrapy编写爬虫,我从中学到了很多东西。另外我个人并没有看过火影,我爬灌篮高手好了。
作者的思路也很值得借鉴。
分析并提取所有可能用到的URL
这里我就直接列出来了,和作者写的可能有点不一样,但大同小异。想看具体分析过程的可以参考作者的原文档。之前我以为这一步是最难的,其实现在看来这一步其实是最简单的。
# 章节的名字
response.xpath("//dl/dd/a[1]/text()").extract()
# 章节的链接
response.xpath("//dl/dd/a[1]/@href").extract()
# 每张的总页数
response.xpath("//tr/td/text()").re("共(\d+)页")[0]
# script脚本内容
response.xpath('//script/text()').extract()
关于代码
代码我就不贴了,原作者在Github上传了完整的源代码,这里我讲一下我对这部分代码的理解。
逻辑
先说下这个爬虫的逻辑, 整个爬取过程中涉及到的URL一共有三种,第一种是每一章的标题和链接,第二种是每一章节中每张图片的链接,作者把每一章的图片分成了两部分:第一张图片和剩下所有的图片,然后分别写了三个parse方法来处理。
在scrapy的sipders模块里,你只需要用yield不断的抛出request或item,其中每个request会根据其包含的url和callback函数再次进入spider的某个处理方法里进行处理;item则会被pipelines.py里的pipeline处理。(想一想前面文章里的那张scrapy机构图)。这里提一点,spider抛出的item会被所有的pipeline轮流处理一变,处理的顺序是根据settings.py里的ITEM_PIPELINES
冒号后面的数字决定,值越小优先级越高。Item的格式在items.py里定义。
细节一点?
comic_spider.py
在parse1中,新建了item对象,每个Item包含了章节名、章节链接、图片链接以及图片保存的路径,但是在parse1中其实只用到了章节名和章节链接两项。这里我们已经知道,章节链接和漫画的主页面的链接的域名是不一样的,因此我们在parse1的最后抛出一个请求,它对应了章节链接以及一个代表item的meta值,同时回调parse2来处理这一系列的请求。
在parse2中,我们通过从request中获取meta值的方式来获取要处理的item对象。在这个方法中,我们要分析出每个章节一共有多少张图片以及第一张图片的URL是什么。这里面涉及到一系列的正则和字符串处理操作,就不细说了,如果看不懂代码,建议在shell模式下把每一步的结果都打印出来看看,可以帮助理解。我们把第一张图片的URL存入item中,然后在最后抛出包含这一章所有图片的URL的请求,比如说如果这一章有122张图片,就会抛出121个请求,当然每个请求都包含了一个代表item的meta值,这些请求都会回调parse3方法。
那么第一张图片是怎么处理呢?很简单直接yield带有第一张图片url的item对象即可,后面会说pipeline是怎么处理这些item的。这里还有一点要注意,虽然每张图片所在的网页是按照1,2,3,4,5…进行编号的,但是图片本身的img_url是由script生成的,它需要你在每个网页里抓取,并存在item[‘img_url’]里。
在parse3中,因为parse2中抛出的每一条请求都对应了一个网页,也就是对应了一张图片,我们只需要将每张网页里的图片的url抓取出来即可,接着我们把这个url存入item中,并抛出item。
pipelines.py
还记得我们在comic_spider.py中抛出的item吗?这里会处理,在这个爬虫中,我们只会用到一个pipeline。首先我觉得需要对所有被抛出的item有个概念,这么多item是分类的?以第一章为例,橄榄高手第一章有122张图片,也就是122个item,其中每个item都带有章节名,章节链接和需要保存的路径(每章第一张图片的item由parse2抛出,其余的由parse3抛出)。那么在pipeline中,我们做的就是将每个item对应的内容保存到本地,假设我们需要保存的路径是d:/
,那么最后我们要写入的地址就是d:/ + 章节名 + 图片名字.jpg
。这里有一点取巧的地方就是我们用图片所在网页里那个数字作为每张图片的名字,因为它正好对应了图片的顺序,所以在图片都下载下来后顺序不会乱。
settings.py
scrapy1.5的settings.py已经有很详细的注释了,当然官方文档有更详细的注释:Settings。
结果
最后自然是运行这个爬虫程序,说实话,看着程序在运行同时文件夹里的内容逐渐增加时心里还是有成就感的,即使这个程序不是我原创的。从文件夹中的内容增加的过程可以看出,scrapy的框架采用的是异步策略(为什么?)。今天还找到一个介绍scrapy的视频,容我观摩一番,再写一篇详细点的关于scrapy的文章。