简单用了回 Scrapy

Dec 29 2019

早前买了本书,是教学 Python 爬虫的。于是翻了翻第一章和最后一章。第一章讲 Python 的一些基础知识,用于后面章节的使用做铺垫,最后一章讲 Scrapy 框架

我本身会一些 Python 开发,于是直接从最后一章开始,试着做一个超简单的 Demo 出来玩一玩,毕竟爬虫还是挺火的,多一门技术傍身也挺好。为此我来了一趟公司,因为网好,而且座椅舒服

按照书上写的把相关的模块都装了,然后通过一行启动命令,生成了工程模板,用法上还是挺舒服的

1
2
3
4
5
6
7
D:\Python_Proj>scrapy startproject zhihu
New Scrapy project 'zhihu', using template directory 'd:\tools\python37\lib\site-packages\scrapy\templates\project', created in:
D:\Python_Proj\zhihu

You can start your first spider with:
cd zhihu
scrapy genspider example example.com

书上讲了一个例子,是爬取知乎 Python 话题下的精华内容。我先把例子的内容简单看了一下,同时在 Chrome 里试着访问作者使用的链接,很不幸,知乎前端团队更新了他们的页面实现,单页的内容不再是按照他写文章时的方式了。当时的方式更简单,每一页都是一个新的页面(URL 不同),通过请求不同的 URL 并直接抓取响应页面的元素就行。现在不同页的 URL 都是一个,发现不能完全按作者的思路来,只能自己重新找找规律

翻了翻其他章节的内容,有提到异步加载,于是看了一下,按照上面的思路,分析了 Chrome 中的 “F12”,发现每次翻页的时候,XHR ( XML HTTP REQUEST )会加载到一个请求,继续分析下去发现它们是有规律的

undefined

检查 Headers 中的 Request URL,我试着在浏览器打开这个链接,返回了一段 json 字符串,用 json 解析器看了一下

undefined

里面的 data 部分,就是每个分页的内容,这里有 10 个元素,表示这一页里有 10 条,其中每一条包含了问题标题,点赞数,评论数,回答摘要等信息。同时也看到了 URL 的规律:limit=10&offset=15

limit 正好就是限制了单页的元素个数,offset 就是偏移量,是用来表示从第几个元素开始返回,所以思路就出来了,通过解析 json,就可以获取到我想要的信息

这些是中间页的内容,首页的情况呢?通过查看网页源代码,发现前 5 条内容都能被找到

undefined

那么首页加载的时候就不是异步的了,是直接随着响应的页面带了这些内容,于是打算先搞个简单的,毕竟我是在做 demo,先把首页的内容扒拉下来看看

Scrapy 生成的工程自带一些 py 文件,不同的文件是做不同用途的,只需要在对应的文件里撸码,它会自动串联起来过程,如下是它的一个结构

undefined

在 items.py 中定义字段,其实就是要抓取的项目,然后在 spiders 目录下去处理请求部分,这里我新增了一个 py 文件 zhihu_python_spider.py, 去请求服务端。而如下的配置是,我把请求并处理后的结果让框架自动写入到 csv 文件,于是在 settings.py 中添加:

1
2
FEED_URI = 'zhihu.csv'
FEED_FORMAT = 'csv'

爬虫中一个比较有用的技能是 xpath 语法,因为定位页面中的元素时需要大量用到它。框架里的 Selector 选择器跟 lxml 三方库中的 Selector 有一些区别,导致我使用时遇到了些问题,于是查询了 Scrapy 的文档解答了遇到的困难

https://scrapy-chs.readthedocs.io/zh_CN/0.24/topics/selectors.html

下面就是抓取的核心代码,有一些知乎提问的 xpath 的位置可能并不完全一致,导致第一个问题的元素被抓到了,第二个问题没有,这里仅为了熟悉用法和思路,就不做深入分析了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def parse(self, response):
item = ZhihuItem()
selector = Selector(response)
links = selector.xpath('//div[@class="List-item TopicFeedItem"]')

for index, link in enumerate(links):
content = '%s, ' % index
try:
title = link.xpath('div/h2/div/a/text()').extract()[0]
content += 'title=%s, ' % title
item['title'] = title
excerpt = link.xpath('div/div[2]/div[2]/span/text()').extract()[0]
content += 'excerpt=%s, ' % excerpt
item['excerpt'] = excerpt
vote_up_count = link.xpath('div/div[2]/div[3]/span/button[1]/text()').extract()[0]
content += 'vote_up_count=%s, ' % vote_up_count
item['voteup_count'] = vote_up_count
comment_count = link.xpath('div/div[2]/div[3]/button[1]/text()').extract()[0]
content += 'comment_count=%s' % comment_count
item['comment_count'] = comment_count
except:
pass
print(content)

yield item

执行时不能再 pycharm 里跑,要通过命令行的 scrapy 去执行

1
2
3
4
5
6
7
D:\Python_Proj\zhihu>scrapy crawl zhihu
2019-12-29 18:21:51 [scrapy.utils.log] INFO: Scrapy 1.8.0 started (bot: zhihu)
2019-12-29 18:21:51 [scrapy.utils.log] INFO: Versions: lxml 4.4.2.0, libxml2 2.9.5, cssselect 1.1.0, parsel 1.5.2, w3lib 1.21.0, Twisted 19.10.0, Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)], pyOpenSSL 19.1.0 (OpenSSL 1.1.1d 10 Sep 2019), cryptography 2.8, Platform Windows-10-10.0.16299-SP0
2019-12-29 18:21:51 [scrapy.crawler] INFO: Overridden settings: {'BOT_NAME': 'zhihu', 'DOWNLOAD_DELAY': 3, 'NEWSPIDER_MODULE': 'zhihu.spiders', 'SPIDER_MODULES': ['zhihu.spiders'], 'USER_AGENT': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36'}
2019-12-29 18:21:51 [scrapy.extensions.telnet] INFO: Telnet Password: 9a977eff99b2e582
2019-12-29 18:21:51 [scrapy.middleware] INFO: Enabled extensions:
...... 省略更多内容

打开工程目录下的 zhihu.csv

undefined

后面会把异步加载的 json 分析也完成,形成一个完整的爬虫工程