jd_crawler
##项目介绍 ###一些不太相关的话
作为此次项目整理的第一个项目,我选择了这个 Python 爬虫。一方面是因为需要一些一手的数据为以后的数据分析使用,另一方面则是来自工作坊大一小朋友们的压力。
在此之前我还真的没有用 Python 写过爬虫,之前所有的爬虫都是用 Java 写的。结果现在大一学生就掌握这项技巧了,自己也应该补上这一课,毕竟做 Data Science 的,一出门大家都是聊 Python。在此前我非常的不喜欢这门语言,虽然大家都说它优雅,做完这个爬虫,我也还是不喜欢,之后的爬虫或者做分布式可能还是以 Java 为主。
这个项目长期以来在我的 WorkSpace 里的名字叫做 Gxy_crawler,其实算是个人帮葛同学的一个小忙,帮人 debug,因为要整理上传 GitHub 的关系,所以整个代码都重构了,完全按照自己的思路来写。
本来想做的很完善的,结果还是有一些虎头蛇尾,很多功能、抛出机制还有缓存处理的一些坑其实都留好了,但是懒得写了,明天开学,还有很多项目等着我去填坑。
###关于项目 爬取京东网站上的相关数据,原始 URL: https://list.jd.com/list.html?cat=737,13297,1300&ev=%402047_15280&go=0&JL=3_产品类型_烟灶套装#J_crumbsBa
这是一个商品类别筛选的 list,内容是烟灶套装。
说实话我真的很懵逼,葛同学你研究这个玩意干啥……你看看别人的京东爬虫项目,先不说内容质量,光看名字就觉得很有意思。京东百万记录分析**人罩杯分布
抓取的主要数据:(都是针对于商品的,以下用“A商品”简化表达)
- A商品名称
- A商品在筛选中的排名
- A商品的价格
- A商品的评论
##项目依赖
只用到了一些Python的很基础的库
- 获取链接:
requests
urllib
urllib2
- 字符处理:
re
json
BeautifulSoup
- 系统设置:
sys
time
random
##京东反爬虫
###URL缺少参数
原始URL:https://list.jd.com/list.html?cat=737,13297,1300&ev=%402047_15280&go=0&JL=3_产品类型_烟灶套装#J_crumbsBa
在原始URL中,没有关于页面的参数,更讨厌的是为什么还有中文,我对 Python
的编码问题真的是头疼,这个事情一会儿会说到。
这个问题其实好解决,点一下下一页,或者随便点个数字不是1的页面,URL会变成如下这样子:
https://list.jd.com/list.html?cat=737,13297,1300&ev=3680%5F6820&
page=1
&sort=sort_totalsales15_desc&trans=1&JL=6_0_0#J_main
本来应该是一行的,但其他的都可以不用管,page
这个参数放心大胆的去遍历就好了。
###循环页面 这个是帮人 debug 的核心内容,当时就是因为这个问题解决不了我才出手的。
葛同学搜了搜资料,小试牛刀,兴奋的抓取到了京东的 html
文件,存储在了本地,命名为 index.html
。
然而循环了一会发现问题出现了,第6页的内容与第1页的完全一样,第11页的内容与第一页一样……以此类推。
如果通过浏览器访问,显然是不会出现这个问题的,所以问题出现在了http请求的 head
上面。根据后面的尝试发现,设置 head
的时候一定要把浏览器中显示的http请求完整的复制下来,只有 User-Agent
是不够的,Cookies
也是一个非常关键的点。当更换开发调试环境或者浏览器环境发生变化的时候,需要更新Cookies
,否则依然会出现上面的问题。
###价格隐藏
在一开始进行本地开发调试的时候,都是把先抓下页面放到本地文件,再进行解析的,问题又出现了。
在本来该出现价格的地方,<em>
标签的值竟然是空的,应该是用 javascript
在页面渲染之后传参进来的。所以直接去抓传参的 javascript
地址,即:
url_price = 'http://p.3.cn/prices/get?type=1&area=1_72_2799&pdtk=&pduid=14834698456071940921980&pdpin=&pdbp=0&skuid=J_' + str(skuid)
域名是 p.3.cn
,在最后要修改一个 skuid
,看起来应该是商品在京东数据库内的编号。
抓取到是 json
格式的,如下,可以用对应的 json
解析器来处理。
[{
"id":"J_2755826",
"p":"4390.00",
"m":"6390.00",
"op":"5690.00"
}]
商品评论也同理:
url_comment = 'http://s.club.jd.com/productpage/p-'\
+ str(skuid) + \
'-s-0-t-0-p-1.html'
抓到的评论数据的 json
我只要了打分为1-5颗星的数据,有所有评论的详细内容,我没有解析。等需要做NLP的时候再去专门做评论的东西。
###IP封锁
因为抓列表页面的时候有设置 time.sleep()
,所以也没有在意这个事情,感觉应该不会出什么岔子。
然而还是在抓价格和评论的时候被封了有好一阵子,后来还是默默的加上了时延。Google 了一些资料说固定间隔的请求也会被封,于是加了 random
来控制时延。
抓价格的时候如果IP被封会返回一个错误的 Json
数据,具体是啥记不清了,好像是什么 "error": "captcha"
类似的这样。抓评论的时候如果被封会直接 Timeout
。
##编码问题与输出规范
这个问题我是真的不想提,心累。
爬虫和解析花了两小时,输出 .csv
遇到的编码问题折磨了我一天。
对 Python
的中文编码真的是吐槽无力,也怪我自己根基不扎实,对各种编码理解不透彻。
主要的问题是三个地方,BeatifulSoup
的中文返回值该怎么处理,怎么把商品名称正确的写进文件里,强制转换格式后编码乱套(诸如 u'\\xe6'
甚至三斜杠的情况我都干出来了)。
还有个问题就是输出流控制的问题,当时考虑到可能会爆堆栈,也为了开发测试方便,每页都会输出一次。
##项目流程
###initial
@param Str url1, url2
@return Int all_page
本来设计的是获取总页面数,然后测试一下循环页面的问题。后来发现循环页面的问题被 Cookie
根治了,就删掉了测试部分。
url1
和 url2
是页面地址在 page
参数前和后的部分,所有涉及到获取页面的函数都会输入这两个参数。
返回 all_page
,即该分类下的页面总数。
###get_html
@param Str url
@return Unicode html
获取 url
然后请求页面,html
是 requests.get()
的返回值。
###get_item
@param Str url1, url2, Int page_curr
@return List p_list
页面解析的部分,拼接 url1
url2
和当前页面数 page_curr
,调用 get_html
获取页面文本,通过正则表达式和 BeautifulSoup
进行页面内容解析,得到商品名称 p_name
和京东编号 p_sku_id
。
再请求价格和评论信息,返回值一同 zip
,得到商品核心数据集 p_list
,完成抓取。
###collect_data
@param Str url1, url2, Int page_num
@function print
主入口函数,执行 initial
之后将 all_page
传入,作为需遍历的页面总数 page_num
。
打开文件流,生成 .csv
文件表头,关闭文件流。
遍历页面,拼接url1
url2
page_num
对应的遍历遍历,传入 get_item
。
得到返回的商品数据集 p_list
,进行标准格式输出。
##写在最后
抓取后的数据样式如 data-set.csv
文件所示,时间和运算资源有限,暂时只抓了前100多条尝了尝咸淡。没什么大问题的话,应该可以遍历完这个分类所有的商品。
对抓取的这些数据用了一下词云工具得到下面的图片,有兴趣的也可以玩一下:词云