11.4 项目实战:爬取京东商城中的书籍信息
11.4.1 项目需求
爬取京东商城中所有Python书籍的名字和价格信息。
11.4.2 页面分析
图11-4所示为在京东网站(http://www.jd.com)的书籍分类下搜索Python关键字得到的页面。
图11-4
结果有很多页,在每一个书籍列表页面中可以数出有60本书,但我们在scrapy shell中爬取该页面时遇到了问题,仅在页面中找到了30本书,少了30本,代码如下:
$ scrapy shell ... >>> url = 'https://search.jd.com/Search?keyword=python&enc=utf-8&book=y&wq=python' >>> fetch(url) ... >>> len(response.css('ul.gl-warp > li')) 30
原来页面中的60本书不是同时加载的,开始只有30本书,当我们使用鼠标滚轮滚动到页面下方某位置时,后30本书才由JavaScript脚本加载,通过实验可以验证这个说法,实验流程如下:
(1)页面刚加载时,在Chrome开发者工具的console中用jQuery代码查看当前有多少本书,此时为30。
(2)之后滚动鼠标滚轮到某一位置时,可以看到JavaScript发送HTTP请求和服务器交互(XHR)。
(3)然后用jQuery代码查看当前有多少本书,已经变成了60,如图11-5所示。
图11-5
既然如此,爬取这个页面时,可以先执行一段JavaScript代码,将滚动条拖到页面下方某位置,触发加载后30本书的事件,在开发者工具的console中进行实验,用document.getElementsByXXX方法随意选中页面下方的某元素,比如“下一页”按钮所在的<div>元素,然后在该元素对象上调用scrollIntoView(true)完成拖曳动作,此时查看书籍数量,变成了60,这个解决方案是可行的。图11-6所示为实验过程。
爬取一个页面的问题解决了,再来研究如何从页面中找到下一页的url地址。
图11-6
如图11-7所示,下一页链接的href属性并不是一个url,而在其onclick属性中包含了一条JavaScript代码,单击“下一页”按钮时会调用函数SEARCH.page(n, true)。虽然可以用Splash执行函数来跳转到下一页,但还是很麻烦,经观察发现,每个页面url的差异仅在于page参数不同,第一页page=1,第2页page=3,第3页page=5……以2递增,并且页面中还包含商品总数信息。因此,我们可以推算出所有页面的url。
图11-7
11.4.3 编码实现
首先,在splash_examples项目目录下使用scrapy genspider命令创建Spider类:
scrapy genspider jd_book search.jd.com
经上述分析,在爬取每一个书籍列表页面时都需要执行一段JavaScript代码,以让全部书籍加载,因此选用execute端点完成该任务,实现JDBookSpider代码如下:
# -*- coding: utf-8 -*- import scrapy from scrapy import Request from scrapy_splash import SplashRequest lua_script = ''' function main(splash) splash:go(splash.args.url) splash:wait(2) splash:runjs("document.getElementsByClassName('page')[0].scrollIntoView(true)") splash:wait(2) return splash:html() end ''' class JDBookSpider(scrapy.Spider): name = "jd_book" allowed_domains = ["search.jd.com"] base_url = 'https://search.jd.com/Search?keyword=python&enc=utf-8&book=y&wq=python' def start_requests(self): # 请求第一页,无须js渲染 yield Request(self.base_url, callback=self.parse_urls, dont_filter=True) def parse_urls(self, response): # 获取商品总数,计算出总页数 total = int(response.css('span#J_resCount::text').extract_first()) pageNum = total // 60 + (1 if total % 60 else 0) # 构造每页的url,向Splash的execute 端点发送请求 foriin range(pageNum): url = '%s&page=%s' % (self.base_url, 2 * i + 1) yield SplashRequest(url, endpoint='execute', args={'lua_source': lua_script}, cache_args=['lua_source']) def parse(self, response): # 获取一个页面中每本书的名字和价格 for sel in response.css('ul.gl-warp.clearfix > li.gl-item'): yield { 'name': sel.css('div.p-name').xpath('string(.//em)').extract_first(), 'price': sel.css('div.p-price i::text').extract_first(), }
解释上述代码如下:
start_requests方法
start_requests提交对第一个页面的请求,这个页面不需要渲染,因为我们只想从中获取页面总数,使用scrapy.Request提交请求,并指定parse_urls作为解析函数。
parse_urls方法
从第一个页面中提取商品总数,用其计算页面总数,之后按照前面分析出的页面url规律构造每一个页面的url。这些页面都是需要渲染的,使用SplashRequest提交请求,除了渲染页面以外,还需要执行一段JavaScript代码(为了加载后30本书),因此使用Splash的execute端点将endpoint参数置为'execute'。通过args参数的lua_source字段传递我们要执行的lua脚本,由于爬取每个页面时都要执行该脚本,因此可以使用cache_args参数将该脚本缓存到Splash服务器。
parse方法
一个页面中提取60本书的名字和价格信息,相关内容大家早已熟悉,不再赘述。
lua_script字符串
自定义的lua脚本,其中的逻辑很简单:
打开页面→等待渲染→执行js触发数据加载(后30本书)→等待渲染→返回html。
另外,京东服务器程序会对请求头部中的User-Agent字段进行检测,因此需要在配置文件settings.py中设置USER_AGENT,伪装成常规浏览器:
# 伪装成常规浏览器 USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko)'
编码和配置的工作已经完成了,运行爬虫并观察结果:
$ scrapy crawl jd_book -o books.csv ... $ cat -n books.scv 1 name,price 2 教孩子学编程 Python语言版,59.00 3 Python机器学习 预测分析核心算法,46.90 4 Python机器学习 预测分析核心算法 9787115433732 [美] Michael,52.44 5 Python数据分析实战 9787115432209,39.50 6 数据科学实战手册 R+Python 9787115426758,39.50 7 Python编程 从入门到实践,89.00 8 Python可以这样学,69.00 9 Python游戏编程快速上手,47.20 10 Python性能分析与优化,45.00 ... 2932 Python语言在Abaqus 中的应用(附CD-ROM光盘1 张),38.40 2933 Python绝技 运用Python成为*级黑客 运用Python成为 级黑客 书籍,46.50 2934 趣学Python编程,42.70 2935 零基础学Python(图文版),65.50 2936 Python程序设计入门到实战,65.50 2937 计算机科学丛书:Python语言程序设计,74.60 2938 面向ArcGIS的Python脚本编程,41.00 2939 Python算法教程 书籍,40.60 2940 Python基础教程+利用Python进行数据分析 Python学习套装 共两册,136.20 2941 包邮 量化投资 以Python 为工具 Python语言处理数据Python金融 量化投,65.80
结果显示,我们成功爬取到了2940本书籍的信息。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论