爬取京东的一些思路
简介
在之前的一个爬取知乎问题和答案的项目中遇到了许许多多的问题,写下此篇文章作为总结和回顾
项目文章 http://blog.csdn.net/sinat_34200786/article/details/78770356
项目地址 https://github.com/Dengqlbq/JDSpider
框架划分
先分析一下项目要爬取的内容:
1. 商品详情
2. 商品评论
3. 评论总结
爬取商品详情首先得有商品详情页的url,然后再进入页面爬取数据
注意:商品url可以简化,这对于后面的爬取很有用
https://item.jd.com/4624505.html?jd_pop=0e7d3959-5331-45e1-a308-e552908569cc&abt=0
简化为:
https://item.jd.com/4624505.html
抽象为:
https://item.jd.com/{0}.html
所以获取商品详情前得获取商品url,商品url的获取则在于京东的搜索页中
通过对比简化后的url可以发现,在搜索页中商品url的关键在于商品id
所以要爬取商品详情先获取商品url,要获取商品url先获取商品id。商品详情在详情页,商品id在搜索页,并且一个搜索页有多个商品id。如果把商品id和商品详情的获取整合在一个爬虫中代码难免显得臃肿,所以将功能分到两个爬虫里面,最终的框架划分如下:
JDUrlsSpider 获取商品id并构造商品详情url和商品评论url
JDDetailSpider 获取商品详情和评论总结
JDCommentSpider 获取商品评论
Q:为什么JDUrlsSpider还要构造商品评论url?①
A:简单讲就是因为获取评论和获取详情一样需要一个入口url,详细的后面会讲到
Q:为什么详情和评论总结都划分到JDDetailSpider?②
A:后面会讲到
获取商品ID
商品id在搜索页,那么先分析搜索页url
搜索页url
https://search.jd.com/Search?keyword=电脑&enc=utf-8&wq=电脑&pvid=5549914a587d4ab2949557695960da56
多翻几页后可以发现url里面的几个重要参数:
keyword 搜索关键词
wq 搜索关键词
page 当前页数
s (这个不好描述,就是page * 60)
所以搜索页url抽象为:
https://search.jd.com/Search?keyword={0}&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq={1}&page={2}&s={3}&click=0
查看页面中的商品id后可以发现其实还是很好找的,都在li标签里面,class也是特殊的,所以写个xpath就能轻松提取。
但是提取后你会发现只获取到了30个商品id,而搜索页中手动数一下怎么也有60个商品id,那剩下的30个哪去了?凭借经验可以判断那是异步加载出来的,在搜索页手动的滚动一下滚动条也可以发现确实如此。
开发者工具–>Network–>XHR,只要找到异步请求的网址就好办了
可以看到当滚动条差不多到底部后拦截到了一个异步请求,请求的url超长,请求的response则证实了这就是我们需要找的url
请求url:
https://search.jd.com/s_new.php?keyword=%E7%94%B5%E8%84%91&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq=%E7%94%B5%E8%84%91&page=2&s=27&scrolling=y&log_id=1514895812.28592&tpl=1_M&show_items=4624505,1378536,5225346,5113808,4824715,4338107,4161503,4335139,4824733,4335674,5002534,5029717,4624543,4274539,5148309,1593516,5148275,3597549,5425401,5189400,5487527,4752515,5454888,5025869,5148299,5352358,5020872,5005929,3915351,5005927
对比之前获取到的30个商品id可以发现,请求url的show_items参数的值就是之气的30个商品id
多翻几页后可以发现url里面的几个重要参数:
keyword 搜索关键词
page 对比后发现就是当前搜索页的页数+1
show_items 第一批获取的30个商品id
所以请求url抽象为:
https://search.jd.com/s_new.php?keyword={0}&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&page={1}&s=26&scrolling=y&log_id=1512092382.36606&tpl=1_M&show_items={2}
所以要获取搜索页中后30个商品id只需要将几个重要参数填好再构造请求然后从response里提取就行了。
获取商品id步骤:
构造搜索页url
获取前30个商品id
构造异步请求
获取后30个商品id
获取商品详情和评论总结
商品详情
要获取的详情数据为:
1. name
2. num
3. price
4. owner(店铺名)
5. jd_sel (是否京东精选)
6. global_buy (是否全球购)
7. flag (自营标志)
如果你的爬虫直接将某个商品详情页直接下载下来进行数据提取,你会发现price永远都是返回空值,按照一般经验判断price是异步请求的。
开发者工具–>Network–>XHR,这时你会发现找不到相关的异步链接。怎么办?price数据一开始不在详情页说明它肯定是后面才请求的数据,既然请求数据那肯定是有相关链接的,XHR那里没有就全部数据找一遍。
开发者工具–>Network–>ALL,仔细找找就能发现请求price数据的链接
请求url:
https://p.3.cn/prices/mgets?callback=jQuery3162769&type=1&area=1_72_2799_0&pdtk=&pduid=525462493&pdpin=&pin=null&pdbp=0&skuIds=J_1378536&ext=11000000&source=item-pc
简化为:
https://p.3.cn/prices/mgets?skuIds=J_1378536
抽象为:
https://p.3.cn/prices/mgets?skuIds=J_{0}
请求返回的是json数据,并且从图中可以看出 “p”所对应的就是price的数据
到这里商品详情的数据就全都获取了。
商品评论总结
①这里解释一下为什么商品详情和商品的评论总结会放在一起,这是因为在获取商品详情price数据时,查找请求price数据的链接过程中发现了一个有意思的链接——获取评论总结的链接
请求url:
https://club.jd.com/comment/productCommentSummaries.action?referenceIds=1378536&callback=jQuery5056364&_=1514983166800
简化为:
https://club.jd.com/comment/productCommentSummaries.action?referenceIds=1378536
抽象为:
https://club.jd.com/comment/productCommentSummaries.action?referenceIds={0}
可以看到请求返回的数据是非常详细的,对于这个意外收获当然是大方笑纳,所以在获取商品详情的时候就顺便把评论总结也搞定了。
获取商品详情和评论总结步骤:
下载商品详情页
获取商品详情
构造price的url
获取price数据
构造comment_excerpt的url
获取comment_excerpt数据
商品评论
一个商品有很多评论,所以商品评论肯定不会一下全部加载出来而是一部分一部分的加载,那么只需要先翻几页评论然后找请求链接就可以了。
开发者工具–>Network–>ALL
可以看到请求返回的数据包含10个评论和评论热词(每次都会返回),随便点开一个评论会发现数据也是很详细的
可以发现评论确实是对应的
请求url:
https://sclub.jd.com/comment/productPageComments.action?callback=fetchJSON_comment98vv34564&productId=1378536&score=0&sortType=5&page=1&pageSize=10&isShadowSku=0&rid=0&fold=1
简化为:
https://sclub.jd.com/comment/productPageComments.action?productId=1378536&score=0&sortType=5&page=1&pageSize=10
抽象为:
https://sclub.jd.com/comment/productPageComments.action?productId={0}&score=0&sortType=5&page={1}&pageSize=10
②这里就可以解释一下为什么JDUrlsSpider需要构造评论url了,可以看到抽象后的评论url需要productId和page两个参数,productId在JDUrlsSpider中直接就有数据,然后将page设为1就可以构造出获取评论的入口url。
说下sortType这个参数,你可以改成其他值试试,你会发现有些值是返回json数据有些不是,这里选择值为5是为了请求返回json数据方便解析,不过实际上证明会有小概率出现返回的不是json数据的情况。
这里还有个问题需要说明——我怎么知道可以获取多少评论?其实在返回数据中都会有一个maxPage的值,根据这里就可以知道我们可以获取多少页的评论了。
获取商品评论步骤:
获取商品评论的入口url
以maxPage构造循环
构造url
获取数据
构造url...
优化
框架划分好了,框架各个部分的思路也有了,接下来只要编码就能把项目完成。
可是想一想项目其实有挺多地方可以优化的,举几个例子:
1. 增加总控
2. 分布式
3. 代理池
增加总控
在框架划分中各个功能已经独立出来:
1. JDUrlsSpider负责根据搜索页url获取该页所有商品id的获取并形成详情url和评论url
2. JDDetailSpider负责根据详情url获取商品详情和评论总结
3. JDCommentSpider负责根据评论url获取商品评论
结构图:
其实很明显的JDUrlsSpider需要根据搜索页url才能知道去哪里获取商品id,如果我们的项目只爬取一个或两三个指定关键词商品则可以写在JDUrlsSpider的配置文件中,但项目考虑的是爬取大量不同关键词商品信息,所以应该增加一个总控端并且总控端应该具备以下功能:
1. 可随时修改关键词
2. 可指定页面数量
优化后的思路就是:
总控端根据配置好的信息不断将搜索页url抛出,JDUrlsSpider不断接收搜索页url进行处理后抛出详情url和评论url,JDDetailSpider和JDCommentSpider则根据相应url进行数据获取
优化后结构图:
分布式
分布式可以解决性能和带宽的瓶颈
试想一下多台机器的多个爬虫一起启动,那爬取速度肯定比单机快得多。
分布式的思路:
1. Master负责url去重,分发,数据存储(数据也可以直接存在Slave机)
2. Slave负责从Master处获取url然后执行自己的任务
这样处理的分布式优点除了快之外,Slave机还可以省去很多配置,只要把代码复制过去直接运行就可以了。
要做成这样的分布式也很简单,scrapy-redis就可以轻松搞定
分布式结构图:
代理池
如果我们的爬虫爬取过于频繁就会触发京东的反爬虫机制,简单粗暴的的服务器会直接封禁IP,只要是这个IP的访问都会直接拒绝,所以我们需要构建一个代理池来应对。
构建代理池可以自己去IP代理商那里买,也可以直接爬取网上免费的代理存起来需要的时候就拿出来用。
买代理优点是方便快捷,只需要调用代理商的API就可以获取代理IP,缺点是贵并且IP的可用性不高。
自己爬代理优点当然是免费了但缺点同样明显——代理IP的可用性超低,还要定期更新存储的代理,识别无效代理并丢弃等等一系列维护。
怎么办好呢?其实万能的Github上已经有很多开源的代理池了,原理都是爬取网上的免费代理,只不过别人已经帮你做好维护工作了,装好就能用。
这里推荐一个Github上的代理池项目:
https://github.com/jhao104/proxy_pool
如果你使用推荐的代理池最好就用gunicorn部署这样可以提高并发性能
注意:使用代理会使爬虫明显变慢,尤其是遇到劣质代理的时候,所以一般最好不用代理
上一篇: io学习:标准输入、标准输出、标准出错
下一篇: 使用Python写CUDA程序的方法