网站介绍&使用痛点
如图1所示,京东拍拍二手于近期上线了一个新平台:拍拍验机,顾客可以通过手机京东客户端,在拍拍验机上挑选购买心仪的二手电子产品(目前均为固定品牌的手机,如二手小米8、二手iPhone 7 Plus)。
在首页,我们可以对手机进行筛选,比如笔者目前倾向于购买二手iPhone 7 Plus 128G 国行,那么就选择“苹果”->“苹果7P”->筛选“国行”和“128G”,如图2所示。确定后下拉页面,就都是符合筛选条件的手机了。
但是,在下拉查看比较手机的过程当中,笔者发现符合筛选条件的手机太多太多了(实测大概400部左右),如果每一个手机都点进去查看对比的话,要把所有手机看一遍,将会花费大量时间,而且往往之前看中的手机再回过头来看,已经显示卖出,从而错失良“机”。此外由于app端服务器不稳定的原因,点击返回键常常显示“加载失败”,导致一切从头开始,需要额外浪费时间再下拉到刚刚浏览的地方,效率十分低下。
当在浏览过程中,看到心仪的手机,点击进去查看手机的详细介绍,会发现有很多的关键参数,比如电池电量、是否过保以及手机各项详细检测情况,如图3所示。如果我们能针对这些参数进行自主筛选,那么就可以迅速排除掉成百上千部手机中的绝大多数,“取其精华,去其糟粕”,只留下少量的“精品”进行挑选,效率将大大提高。
但是网站提供的筛选条件少得可怜,只有“价格”、“渠道”、“内存”三个筛选条件,既然如此,我们就要自己动手丰衣足食了。
网站分析
在浏览拍拍验机的主页后,我们发现它一次只会加载20部手机信息,并且加载的方式不是翻页,而是将页面滑动到底部,那么爬取静态网页数据的那一套方法就不能用在现在这种动态网页上(静态网页用Chrome浏览器右键“查看网页源代码”就能看到所有的网页数据,但是动态网页用这种办法是看不到的),这对获取新增的手机数据带来了困难。这里使用Chrome浏览器的“检查”功能解析AJAX动态加载地址,进而找到加载的数据。
使用Chrome浏览器打开拍拍验机的主页,页面任意处右键,单机“检查”-> Network-> Ctrl + R刷新,每当滑动网页页面到底部时,Network下方会出现新加载的内容,单击 JS,单击左侧的一个_JPx(最好是选最底部的),单击 Headers,可以看到Request URL,如图4所示,后面要用到。注意链接里必须包含order=2_1,因为在实践中发现有些链接包含的是order=4_2,包含order=4_2的链接不是我们所需要的。
可以发现,每当网页下拉加载新的手机数据的时候,JS里的_JPx就会增加一项,每次下拉刷新得到的新数据就是请求了
https://bizgw.jd.com/app/inspected/v1/list?pageSize=20&pageNo=1&brand=Apple&model=iphone7%20plus&order=2_1&buychannel=%E5%9B%BD%E8%A1%8C&capacity=128g&callback=__jp7
这个网页的json数据。单击Preview,可以看到json数据的结构,如图5所示。
其中,我们可以看到新加载的每一个手机的唯一的"commodityId",后面要用到。将网页一直向下拉,JS中的_JPx就会一直增加,直到网页拉到底。如图6所示,可以看到_JPx增加到_JP22,_JP7到_JP22中就包含了所有的手机的"commodityId"。之后就需要通过Python爬虫将所有的"commodityId"收集起来。
接着点击进入任意一个手机的详细介绍页面,同样的方法,我们找到商品详情介绍页面的Request URL,详情介绍页面的数据就是请求了
https://bizgw.jd.com/commodityInspected/view?callback=jQuery34005582774663554724_1558160057557&commodityId=5583330&optSource=3&_=1558160057558
这个网页的json数据,如图7所示。
在Preview中可以看到json数据的结构,包括保修期、电池电量、验机报告等详细检测数据,甚至能看到网页中看不到的信息,比如“验机中心”,如图8所示。
通过Python爬虫,根据单个商品json数据的结构中的各种详细参数进行筛选,就能迅速排除掉大量干扰项,达到“取其精华,去其糟粕”的目的了。
项目实施
1. 爬取commodityId
1.1 失败的爬取思路
通过分析,我们得到_JP7到_JP22包含了所有的手机的"commodityId"。那么就采用常用的requests.get()的方法,通过for循环,将每一个_JPx里的所有"commodityId"爬取出来保存到一个列表中,具体实现代码如下:
#-*- coding: utf-8 -*-
import requests
import re
#定义函数scrapy
def scrapy(link):
headers = {
#User-Agent和Referer的信息在Network->Headers->Request Headers
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36',
'Referer':'https://paipai.m.jd.com/direct/?lng=119.954575&lat=31.686691&un_area=12_978_4459_53900&sid=b4d89db2c431e6c9631e9859ecdc099w&_ts=1557926133167&utm_source=iosapp&utm_medium=appshare&utm_campaign=t_335139774&utm_term=CopyURL&ad_od=share&ShareTm=kL%2BfusKf1qywVd%2BwTkAVtGYRXA2gzBQqFj4A0YuGC1teMZhlGn5CDqBdFZvEgw8/2zhgK18/FX3A93HO04B6bPi7qv8sqdX5i/bUuilkbnOp3GDR%2BoHeKPmG1hagOq5B0WaZTCW%2B7KvJdTxfEYpNNpGoA%2BsAMlUKlUnkVPBzD2g='
}
r = requests.get(link, headers=headers)
return(r.text)
#将_jp7到_JP22的所有Request URL保存到一个列表中
Req_URL = []
for i in range(7, 23):
Req_URL.append('https://bizgw.jd.com/app/inspected/v1/list?pageSize=20&pageNo=1&brand=Apple&model=iphone7%20plus&order=2_1&buychannel=%E5%9B%BD%E8%A1%8C&capacity=128g&callback=__jp'+str(i))
#将_jp7到_JP22中的的所有commodityId保存到一个列表中
all_Id = []
for i in Req_URL:
m_findall = []
html = ""
html = scrapy(i)
m_findall = re.findall(r'"commodityId":.{7}', html)#目前网站上机器码暂为7位数
for id in m_findall:
all_Id.append(id[-7:])
通过上述代码,就可以将所有的商品ID储存到列表all_Id当中,但是上述代码有两个问题:
1. 运行代码后,发现每一个_JPx中的商品Id都是相同的,原因尚不清楚;
2. 拍拍验机平台的手机数量是实时刷新的,现在我们看到的是所有商品Id保存在_JP7到_JP22,也许一小时后就变成_JP8到_JP30。
1.2 改进后的巧妙的爬取思路
上述两个问题,第二个问题还好解决,但是第一个问题百思不得其解,涉及比较深入,对于笔者这种学习Python爬虫不足一月的人来说很难解,于是乎转而在Request URL上动心思。原版的Request URL为:
https://bizgw.jd.com/app/inspected/v1/list?pageSize=20&pageNo=1&brand=Apple&model=iphone7%20plus&order=2_1&buychannel=%E5%9B%BD%E8%A1%8C&capacity=128g&callback=__jp7
笔者将此链接进行一定的删改,发现并不影响request.get()的结果,于是乎将多余的删掉,得到:
https://bizgw.jd.com/app/inspected/v1/list?pageSize=20&brand=Apple&model=iphone7%20plus&order=2_1&buychannel=%E5%9B%BD%E8%A1%8C&capacity=128g
此时能爬取出20个商品ID。这时发现是不是pageSize=20限制了爬出的ID数量?遂改成pageSize=10000,神奇的事情发生了,400多个商品ID被一次全部爬出!实现代码如下&#x