Spyder 下使用 Scrapy 开发爬虫之腾讯视频抓取

我目前主要的学习资源是 Scrapy 官方文档 以及 百度,个人比较喜欢去官网,虽然全英文,学习起来比起看别人的中文博客要慢很多,但是毕竟官网上给出的解决方案都是保持更新的,现在的很多博客都是一两年前的文章,随着版本跟新很多方案可能不再适用,所以我一边学习,一边更新博客,尽量语言简洁,不扯duzi,但又尽量 step by step,提高内容的实用性。

为了使用 Scrapy 框架开发,同时尽量减少学习阻碍,下面给出一份大致的 Scrapy 框架流程。

首先,查看 Spyder 上已经创建好的项目的结构。


经常编辑的文件就是其中的:

[python]  view plain  copy
  1. scrapy.cfg  
  2. items.py  
  3. piplines.py  
  4. settings.py  
  5. video_spider.py # 这是你用模板创建出来的爬虫文件  

Scrapy 大致流程是:

  • items.py 想要爬取的页面中的内容,比如对于腾讯视频,我想要爬取网页上列出的电视剧的名字和豆瓣评分
  • video_spider.py 爬虫本体,执行爬取过程,在哪个网页上爬取什么内容,返回到哪里,都要自己编写
  • piplines.py 处理返回结果,爬虫爬完后返回的结果如果想用 txt、excel、mysql 等格式存储,就在这里写


以上就是 Scrapy 框架的一个最简单版本的理解,其中的主要环节就是爬虫本体,它封装好了诸如 urllib 等依赖库,无需关心细节。使用 Scrapy 的感觉就像是一说到吃饭只要能够想到要用筷子(或者刀叉或者手等)把面前的米饭(面包、面条等)送进嘴里咀嚼后吞下去一样,问题就能够解决。而筷子(工具)从哪来、怎么做、好不好用等等细节无需关心,只要能够想到最外层(框架)的思路,问题就能够解决。这也就是框架的强大之处。

  • 找到要爬取的内容

首先,去腾讯视频网站上查看网页结构,具体的方法非常简单,就是打开百度,搜素腾讯视频,进入官网,鼠标移动到电视剧上然后在显示出的小面板上点击全部电视剧即可。可能是因为网页进行了改版,现在想要查看所有的电视剧列表不太方便了,入口如下,在左边蓝色箭头位置。


点击最受好评后可以看到这样的页面。

这样的页面结构比较清晰,每一页包含30个电视剧,每一个电视剧包含名称,豆瓣分数,主演,图片等等内容,而且可以想象,页面背后的源码应该也是比较规整的,不同的电视剧之间的源码结构应该都一样,注意这里我已经得到了第一个关键数据,就是这个页面的 url,https://v.qq.com/x/list/tv?offset=0&sort=16。使用右键查看源码,可以看到 html 源码非常多而且看起来比较杂乱,不过没关系, ctrl + F 搜索“权力的游戏第四季”,可以发现匹配的地方只有三处:


其中前两个字符串都是在标签内部作为属性值,之所以是中文属性值,可能是考虑到了 SEO,也就是搜索引擎优化。比如在百度上直接搜索“权力的游戏第四季”,腾讯视频是排在第一位的。


好了,直接看第三个字符串的位置,为了方便查看源码,我把关键部分打印在下面,同时对齐了下格式:

[html]  view plain  copy
  1. <div class="figure_title_score">  
  2.     <strong class="figure_title">  
  3.         <a href="https://v.qq.com/x/cover/6rk0jemko5uecjh.html" target="_blank" title="权力的游戏第四季" _stat2="videos:title">权力的游戏第四季</a>  
  4.     </strong>  
  5.                       
  6.     <div class="figure_score">  
  7.         <em class="score_l">9</em>  
  8.         <em class="score_s">.6</em>  
  9.     </div>  
  10.                       
  11. </div>  
可以看到,我本来想要抓取的内容:电视剧名字和豆瓣分数,居然刚好都是在同一个父节点
[html]  view plain  copy
  1. <div class="figure_title_score">  

下面。而且通过搜索其他电视剧名称,发现他们的结构确实都是和上面一样的。现在,我把自己想象成电脑,用程序的思路,去精确匹配到这两个值。

  • 首先,我要寻找一个 class 属性为 figure_title_score 的 div 标签,但是找到第一个匹配的标签后我不会善罢甘休,继续寻找匹配的标签直到我遍历完整个 html 文件,结束后,我得到了 30 个结构相同的 div 标签
  • 第一个 div 标签内,我要寻找 strong 标签,遍历整个标签,我只发现了一个匹配的 strong 标签
  • 在这个 strong 标签内,我要寻找 a 标签,我只找到一个,标签内容是“权力的游戏第四季”
  • 退出 strong 标签,找到与之同级的 div 标签,只有一个
  • 进入这个 div 标签,寻找 class 属性为 score_l 的 em 标签,只有一个,内容是 9,然后寻找 class 属性为 score_s 的 em 标签,只有一个,内容是 .6
  • 第一个标签搜索完毕,进入下一个 div 标签
  • 重复。。。
上面的思路写成文字略显幼稚,但是接近程序的执行过程。那么实现上面思路的程序或者称为包或者是库,其实有很多,比如最原始的可以用 re 库,也就是正则表达式匹配,或者使用 Scrapy 框架提供的 xpath 或者 css,或者导入第三方库 beautiful soup等等,都可以,他们就像是吃饭的工具一样,用筷子也好,刀叉也好,觉得哪个好用就用哪个。

好了,网页源码分析到这里可以告一段落,因为我只想要这一个页面的信息,其他页面不用再分析。

到这里,爬虫的主体思路已经有了,爬虫的设计文档可以说也已经写出来了,现在是实现文档。

  • 制造爬虫

根据 Scrapy 框架的思路,首先我要在 items.py 中写入我想要爬取的内容,在这里其实就是电视剧标题和豆瓣分数,给他们分别取个易读的名字,不妨称为 video_name 和 douban_score。源码如下:

[python]  view plain  copy
  1. # -*- coding: utf-8 -*-  
  2.   
  3. # Define here the models for your scraped items  
  4. #  
  5. # See documentation in:  
  6. # https://doc.scrapy.org/en/latest/topics/items.html  
  7.   
  8. import scrapy  
  9.   
  10.   
  11. class TencentVideoItem(scrapy.Item):  
  12.     # define the fields for your item here like:  
  13.     # name = scrapy.Field()  
  14.     video_name = scrapy.Field()  
  15.     douban_score = scrapy.Field()  
  16.     # pass  

然后,进入 video_spider.py 中,编写我的爬虫。这个文件是通过 scrapy genspider video_spider v.qq.com 命令自动创建的。里面的代码可以说是模板,只需要修改关键部分即可,有点像是做填空,或者像是填表格。

[python]  view plain  copy
  1. # -*- coding: utf-8 -*-  
  2. import scrapy  
  3. from tencent_video.items import TencentVideoItem # 导入 items.py 中的 TencentVideoItem  
  4.   
  5. ''''' 
  6. 爬取腾讯视频网站上的最受好评的电视剧, 
  7. 网址:http://v.qq.com/x/list/tv?offset=0&sort=16, 
  8. 爬取的内容包括电视剧名字, 豆瓣评分 
  9. '''  
  10.   
  11. class VideoSpiderSpider(scrapy.Spider):  
  12.     name = 'video_spider'  
  13.     allowed_domains = ['v.qq.com'# 爬虫只能在这个域内爬行  
  14.     start_urls = ['http://v.qq.com/x/list/tv?offset=0&sort=16'# 目前只爬行一页  
  15.   
  16.     # 解析爬取的 html 内容  
  17.     def parse(self, response):  
  18.         subselect = response.xpath('//div[@class="figure_title_score"]'# 嵌套匹配  
  19.         items = []  
  20.         for sub in subselect:  
  21.             item = TencentVideoItem() # 结构化 item  
  22.             item['video_name'] = sub.xpath('./strong/a/text()').extract()[0# 返回的是 list 所以 [0] 表示获取列表中的第一个元素,也就是字符串  
  23.             item['douban_score'] = sub.xpath('./div/em[@class="score_l"]/text()').extract()[0] + \  
  24.             sub.xpath('./div/em[@class="score_s"]/text()').extract()[0]  
  25.             items.append(item) # 存入 list  
  26.         return items  
上面的代码中,我使用的是 xpath 匹配内容, xpath 是一种标准格式,语法可以参考  W3School 相应内容

数据已经有了,为了初步验证程序没有什么语法错误等,可以使用 scrapy shell 命令进行初步的测试,在 prompt 中输入:

[python]  view plain  copy
  1. scrapy shell "http://v.qq.com/x/list/tv?offset=0&sort=16"  
注意,如果是在 Windows 系统下,后面的 url 一定要用双引号括起来,否则会丢弃掉后面的 
[python]  view plain  copy
  1. &sort=16  

打印结果如下:

[python]  view plain  copy
  1. >scrapy shell "http://v.qq.com/x/list/tv?offset=0&sort=16"  
  2. 2018-04-28 21:14:07 [scrapy.utils.log] INFO: Scrapy 1.5.0 started (bot: tencent_video)  
  3. 2018-04-28 21:14:07 [scrapy.utils.log] INFO: Versions: lxml 4.1.0.0, libxml2 2.9.4, cssselect 1.0.3, parsel 1.4.0, w3lib 1.19.0, Twisted 17.5.0, Python 3.6.3 |Anaconda custom (64-bit)| (default, Oct 15 201703:27:45) [MSC v.1900 64 bit (AMD64)], pyOpenSSL 17.2.0 (OpenSSL 1.0.2o  27 Mar 2018), cryptography 2.0.3, Platform Windows-10-10.0.15063-SP0  
  4. 2018-04-28 21:14:07 [scrapy.crawler] INFO: Overridden settings: {'BOT_NAME''tencent_video''DUPEFILTER_CLASS''scrapy.dupefilters.BaseDupeFilter''LOGSTATS_INTERVAL'0'NEWSPIDER_MODULE''tencent_video.spiders''ROBOTSTXT_OBEY'True'SPIDER_MODULES': ['tencent_video.spiders']}  
  5. 2018-04-28 21:14:07 [scrapy.middleware] INFO: Enabled extensions:  
  6. ['scrapy.extensions.corestats.CoreStats',  
  7.  'scrapy.extensions.telnet.TelnetConsole']  
  8. 2018-04-28 21:14:07 [scrapy.middleware] INFO: Enabled downloader middlewares:  
  9. ['scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware',  
  10.  'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',  
  11.  'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware',  
  12.  'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware',  
  13.  'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware',  
  14.  'scrapy.downloadermiddlewares.retry.RetryMiddleware',  
  15.  'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware',  
  16.  'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware',  
  17.  'scrapy.downloadermiddlewares.redirect.RedirectMiddleware',  
  18.  'scrapy.downloadermiddlewares.cookies.CookiesMiddleware',  
  19.  'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware',  
  20.  'scrapy.downloadermiddlewares.stats.DownloaderStats']  
  21. 2018-04-28 21:14:07 [scrapy.middleware] INFO: Enabled spider middlewares:  
  22. ['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware',  
  23.  'scrapy.spidermiddlewares.offsite.OffsiteMiddleware',  
  24.  'scrapy.spidermiddlewares.referer.RefererMiddleware',  
  25.  'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware',  
  26.  'scrapy.spidermiddlewares.depth.DepthMiddleware']  
  27. 2018-04-28 21:14:07 [scrapy.middleware] INFO: Enabled item pipelines:  
  28. []  
  29. 2018-04-28 21:14:07 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6023  
  30. 2018-04-28 21:14:07 [scrapy.core.engine] INFO: Spider opened  
  31. 2018-04-28 21:14:07 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://v.qq.com/robots.txt> (referer: None)  
  32. 2018-04-28 21:14:08 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://v.qq.com/x/list/tv?offset=0&sort=16> (referer: None)  
  33. 2018-04-28 21:14:09 [traitlets] DEBUG: Using default logger  
  34. 2018-04-28 21:14:09 [traitlets] DEBUG: Using default logger  
  35. [s] Available Scrapy objects:  
  36. [s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)  
  37. [s]   crawler    <scrapy.crawler.Crawler object at 0x00000157C6E7BB70>  
  38. [s]   item       {}  
  39. [s]   request    <GET http://v.qq.com/x/list/tv?offset=0&sort=16>  
  40. [s]   response   <200 http://v.qq.com/x/list/tv?offset=0&sort=16# 这里说明 http 请求成功  
  41. [s]   settings   <scrapy.settings.Settings object at 0x00000157C97C8C18>  
  42. [s]   spider     <VideoSpiderSpider 'video_spider' at 0x157c9a55b70>  
  43. [s] Useful shortcuts:  
  44. [s]   fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)  
  45. [s]   fetch(req)                  Fetch a scrapy.Request and update local objects  
  46. [s]   shelp()           Shell help (print this help)  
  47. [s]   view(response)    View response in a browser  
[python]  view plain  copy
  1. >In [1]:   
然后看一下结果是否正确,这时 prompt 控制台已经进入了 python 环境下,所以可以直接一行一行输入代码:
[python]  view plain  copy
  1. In [5]: response.xpath('//div[@class="figure_title_score"]/strong/a/text()').extract()  
  2. Out[5]:  
  3. ['权力的游戏第四季',  
  4.  '风骚律师 第二季',  
  5.  '鬼吹灯之精绝古城',  
  6.  '权力的游戏第六季',  
  7.  '琅琊榜',  
  8.  '我亲爱的朋友们',  
  9.  '黎明之前',  
  10.  '权力的游戏第三季',  
  11.  '摩登家庭第六季',  
  12.  '权力的游戏第二季',  
  13.  '摩登家庭第五季',  
  14.  '摩登家庭第四季',  
  15.  '摩登家庭第三季',  
  16.  '摩登家庭第二季',  
  17.  '小戏骨水浒传',  
  18.  '小戏骨红楼梦刘姥姥进大观园',  
  19.  '权力的游戏第七季',  
  20.  '信号 Signal',  
  21.  '权力的游戏第一季',  
  22.  '权力的游戏第五季',  
  23.  '战长沙',  
  24.  '父母爱情',  
  25.  '毛骗 第2季',  
  26.  '茶馆',  
  27.  '情满四合院',  
  28.  '一起同过窗',  
  29.  '真探第一季',  
  30.  '硅谷第二季',  
  31.  '风骚律师 第一季',  
  32.  '摩登家庭 第7季']  
  33.   
  34. In [6]:  

我这里稍微变了一下 xpath 语句,打印出这一页所有的电视剧名字,注意 xpath().extract() 方法返回结果是一个 list

这说明程序应该没有问题,接下来保存数据。

  • 保存数据

在保存数据前,需要设置 settings.py 指定数据处理者

[python]  view plain  copy
  1. # 找到下面一段代码,并取消注释  
  2. # Configure item pipelines  
  3. # See https://doc.scrapy.org/en/latest/topics/item-pipeline.html  
  4. ITEM_PIPELINES = {  
  5.     'tencent_video.pipelines.TencentVideoPipeline'300,  
  6. }  

然后编写 piplines.py,这里我用 csv 格式保存数据

[python]  view plain  copy
  1. # -*- coding: utf-8 -*-  
  2.   
  3. # Define your item pipelines here  
  4. #  
  5. # Don't forget to add your pipeline to the ITEM_PIPELINES setting  
  6. # See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html  
  7. import csv  
  8.   
  9.   
  10. class TencentVideoPipeline(object):  
  11.       
  12.     def __init__(self):  
  13.         with open("tencent-video.csv""a", newline='') as csvfile:  
  14.             writer = csv.writer(csvfile)  
  15.             writer.writerow(["电视剧""豆瓣评分"])  
  16.       
  17.     def process_item(self, item, spider):  
  18.         video_name = item['video_name']  
  19.         douban_score = item['douban_score']  
  20.         with open("tencent-video.csv""a", newline='') as csvfile:  
  21.             writer = csv.writer(csvfile)  
  22.             writer.writerow([video_name, douban_score])  
  23.         return item  

上面添加了类的解析函数,以便只生成一次表头,process_item 在这里会被循环调用 30 次。

猜你喜欢

转载自blog.csdn.net/zbrj12345/article/details/80540078