/jd_crawler

a python crawler to jd.com

Primary LanguagePython

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商品”简化表达)

  1. A商品名称
  2. A商品在筛选中的排名
  3. A商品的价格
  4. A商品的评论

##项目依赖

只用到了一些Python的很基础的库

  1. 获取链接: requests urllib urllib2
  2. 字符处理: re json BeautifulSoup
  3. 系统设置: 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 根治了,就删掉了测试部分。

url1url2 是页面地址在 page 参数前和后的部分,所有涉及到获取页面的函数都会输入这两个参数。

返回 all_page,即该分类下的页面总数。

###get_html

@param Str url
@return Unicode html

获取 url 然后请求页面,htmlrequests.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多条尝了尝咸淡。没什么大问题的话,应该可以遍历完这个分类所有的商品。

对抓取的这些数据用了一下词云工具得到下面的图片,有兴趣的也可以玩一下:词云

Wordcloud