来~我这有个需求你做一哈
爬取京东上所有华为手机的折扣价(有则获取,无则不用)、价格、手机品类、评论数量、评论标签
注意点
-
这里对于价格的区分是这样的,对于有明显标识“折扣价”的取其数值作为折扣价,此时价格就是其原价;而对于没有明显标识的只有一个价格的情况,在后面我们进行数据提取时再提
-
手机品类就是这个商品的名称罢,同时包括手机所有的规格配置,如魅惑红4G+32G、银灰色 8G+128G等
栗子
-
评论数及评论标签则是在这里
敲键盘之前
手机ID、手机名称获取
老样子,拿到一个需求后我们要分析需求对应的数据都要在哪里获取
首先我们看一下这个列表页中的手机信息都是哪冒出来的
找了找网页源码,发现只有30条记录,But 这一页我掰着手指数了数有60个手机,另外30个被吃了?
不可能的,那么看看是不是动态加载的吧
果不其然,我们在 s_new.php....这个接口中找到了剩下动态加载的 30 条手机数据,而实际上每个页面的 60 条数据都是通过这个类似的接口加载的,每批 30 个,分两批;这两批的接口 url 还有点差异
这两次我们可以看作是单数页跟双数页,明显双数页比单数页多了好一串数字,实际上这些数字经过比较可以发现就是那单数页手机的 ID ,而且在实际调用时只要改变页数及偏移量,保持这样的接口,而不用改动这些 ID
self.d_page_url = 'https://search.jd.com/s_new.php?keyword=%E5%8D%8E%E4%B8%BA&enc=utf-8&' \
'qrst=1&rt=1&stop=1&vt=2&bs=1&wq=%E5%8D%8E%E4%B8%BA&cid2=653&cid3=655&' \
'ev=exbrand_%E5%8D%8E%E4%B8%BA%EF%BC%88HUAWEI%EF%BC%89%5E&page={}&s={}&click=0'
self.s_page_url = 'https://search.jd.com/s_new.php?keyword=%E5%8D%8E%E4%B8%BA&enc=utf-8&qrst=1&rt=1&' \
'stop=1&vt=2&bs=1&wq=%E5%8D%8E%E4%B8%BA&cid2=653&cid3=655&ev=exbrand_%E5%8D%8E%E4%B8' \
'%BA%EF%BC%88HUAWEI%EF%BC%89%5E&page={}&s={}&scrolling=y&log_id=1546069310.93962&tpl=3_M&' \
'show_items=7694047,5821455,7421462,8735304,100000822981,6946605,7081550,100000650837,' \
'7479912,100000766433,100000820311,100002293114,8240587,100001467225,6733024,6703015,' \
'34593872687,100000972490,6840907,5963066,6001239,28245104630,8058010,5826236,100000767811,' \
'8485229,7123633,100000084109,6737464,6055054'
我们可以从这个接口中获取每个手机的 ID (来构成每个手机的 url ),手机的名称
手机折扣价、价格获取
这里我们就来讲一讲手机价格,手机的价格呢在点击进入一个手机后,我们需要在另一个接口中获取:
https://c0.3.cn/stock?skuId=7694047&cat=9987,653,655&venderId=1000000904&area=1_72_2799_0&buyNum=1&choseSuitSkuIds=&extraParam={%22originid%22:%221%22}&ch=1&fqsp=0&pduid=14925274412821890795342&pdpin=jd_707f2cd751b1b&callback=jQuery7641519
首先这个 URL 本身带有花括号 { } 这也就是说我们在用 format 格式化的时候会遇到问题,怎么办呢,这里我们要转义一下这个花括号,{{ }} 通过两个括号的形式来转义
self.phone_price = 'https://c0.3.cn/stock?skuId={}&cat=9987,653,655&venderId=1000004259&area=1_72_2799_0&' \
'buyNum=1&choseSuitSkuIds=&extraParam={{%22originid%22:%221%22}}&ch=1&fqsp=0&' \
'pduid=1566822370&pdpin=jd_707f2cd751b1b&callback=jQuery7969932'
再来看看这里的 jdPrice ,m 对应的值应该是一个最大值,op 对应之前的价格,p 对应现在的价格;当然这里是两者相等,也就是说这款手机没有过降价,所以我们的折扣价就没有,价格就给定这里的 p ;那若是 p < op 的情况就很明显,给定折扣价为 p ,价格为 op
手机评论数、评论标签获取
来看看剩下的评论数及评论标签,一样我们通过找加载的接口可以发现
https://sclub.jd.com/comment/productPageComments.action?callback=fetchJSON_comment98vv8623&productId=8758880&score=0&[图片上传中...(2019-01-11_225325.png-e1e00f-1547220671822-0)]
sortType=5&page=0&pageSize=10&isShadowSku=0&fold=1
这个接口中也有评论数,但是最后分析可能是有很多的爬虫在爬取京东的商品评论来做用户分析,然后这个接口就做了IP的反爬处理,同一个IP访问的次数有限,一定次数后将无法返回数据,你可以试试现在是否还是这样...总之比较蛋疼,就没有接着获取评论的标签
这里需求就改为只获取评论数(嗯我的地盘我说了算,当然也可以用过代理IP池的方法获取评论标签)
但是光是华为的手机我最后爬下来就有3万+的数据,所以这里我又找到一个接口来获取评论数
手机的不同规格获取
接下来还有个问题就是手机的不同规格应该如何获取了,这时候我是通过找到网页源码中的这里
这里的 skuId 就是各种规格的手机的 ID 了,所以获取也就不在话下(关于这一点,应该有对应的接口,大家可以找找看呀)
开始敲键盘
初始化的一些链接
# 搜索第一页的链接 用于获取总页数
self.search_url = 'https://search.jd.com/search?keyword=%E5%8D%8E%E4%B8%BA&enc=utf-8&qrst=1&rt=1&' \
'stop=1&vt=2&bs=1&wq=%E5%8D%8E%E4%B8%BA&cid2=653&' \
'cid3=655&ev=exbrand_%E5%8D%8E%E4%B8%BA%EF%BC%88HUAWEI%EF%BC%89%5E&page=1&s=1&click=0'
# 单页
self.d_page_url = 'https://search.jd.com/s_new.php?keyword=%E5%8D%8E%E4%B8%BA&enc=utf-8&' \
'qrst=1&rt=1&stop=1&vt=2&bs=1&wq=%E5%8D%8E%E4%B8%BA&cid2=653&cid3=655&' \
'ev=exbrand_%E5%8D%8E%E4%B8%BA%EF%BC%88HUAWEI%EF%BC%89%5E&page={}&s={}&click=0'
# 双页
self.s_page_url = 'https://search.jd.com/s_new.php?keyword=%E5%8D%8E%E4%B8%BA&enc=utf-8&qrst=1&rt=1&' \
'stop=1&vt=2&bs=1&wq=%E5%8D%8E%E4%B8%BA&cid2=653&cid3=655&ev=exbrand_%E5%8D%8E%E4%B8' \
'%BA%EF%BC%88HUAWEI%EF%BC%89%5E&page={}&s={}&scrolling=y&log_id=1546069310.93962&tpl=3_M&' \
'show_items=7694047,5821455,7421462,8735304,100000822981,6946605,7081550,100000650837,' \
'7479912,100000766433,100000820311,100002293114,8240587,100001467225,6733024,6703015,' \
'34593872687,100000972490,6840907,5963066,6001239,28245104630,8058010,5826236,100000767811,' \
'8485229,7123633,100000084109,6737464,6055054'
# 来源页,需要在访问单双页的接口时带上
self.referer = 'https://search.jd.com/search?keyword=%E5%8D%8E%E4%B8%BA&enc=utf-8&' \
'qrst=1&rt=1&stop=1&vt=2&bs=1&wq=%E5%8D%8E%E4%B8%BA&cid2=653&cid3=655&' \
'ev=exbrand_%E5%8D%8E%E4%B8%BA%EF%BC%88HUAWEI%EF%BC%89%5E&page={}&s={}&click=0'
# 评论数接口
self.good_url = 'https://club.jd.com/comment/productCommentSummaries.action?referenceIds={}' \
'&callback=jQuery5661745&_=1540019538438'
# 评论标签
self.comments_type = 'https://sclub.jd.com/comment/productPageComments.action?' \
'callback=fetchJSON_comment98vv28643&productId={}&score=0&sortType=5' \
'&page=0&pageSize=10&isShadowSku=0&fold=1'
self.user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' \
'Chrome/70.0.3538.102 Safari/537.36'
# 价格
self.phone_price = 'https://c0.3.cn/stock?skuId={}&cat=9987,653,655&venderId=1000004259&area=1_72_2799_0&' \
'buyNum=1&choseSuitSkuIds=&extraParam={{%22originid%22:%221%22}}&ch=1&fqsp=0&' \
'pduid=1566822370&pdpin=jd_707f2cd751b1b&callback=jQuery7969932'
请求第一页得到总页数,之后按照单双页的不同url,进行格式化,注意访问时需要带上,referer 以及 ua
def gethtml2(self):
s = requests.session()
# 需要带上ua
r_page = s.get(self.search_url, headers={'user-agent': self.user_agent})
page = re.findall(r'<span class="fp-text">.*?<b>1</b><em>/</em><i>(\d+)</i>.*?</span>', r_page.text, re.S)
pages = int(page[0])*2 + 1
print(u'共' + str(pages) + u'页')
for page in range(115, pages):
# 单数页
if page % 2 == 1:
referers = self.referer.format(page, 30*(int(page) - 1) + 1)
header = {
'referer': referers,
'user-agent': self.user_agent,
}
r = s.get(self.d_page_url.format(page, 30 * (int(page) - 1) + 1), headers=header)
self.gethtml1(s, page, r)
if page % 2 == 0:
referers = self.referer.format(page - 1, 30 * (int(page) - 2) + 1)
header = {
'referer': referers,
'user-agent': self.user_agent,
}
r = s.get(self.s_page_url.format(page, 30 * (int(page) - 1) + 1), headers=header)
self.gethtml1(s, page, r)
提取数据
def gethtml1(self, s, page, r):
# 一页30个手机
info_list = re.findall(r'<li class="gl-item" data-sku="(.*?)".*?>.*?<div class="p-price">(.*?)</div>'
r'.*?<em>(.*?)</em>.*?<a id="J_comment_.*?>.*?</a>.*?</li>', r.text, re.S)
for infos in info_list:
# 每一个手机
info_id = infos[0]
info_url = 'https://item.jd.com/{}.html'.format(infos[0])
print(info_url)
name = self.name_tool(infos[2])
r_refprice = s.get(self.phone_price.format(info_id))
ref_prices = re.findall(r'"jdPrice":{.*?"op":"(.*?)".*?"p":"(.*?)".*?}', r_refprice.text, re.S)
price, ref_price = '', ''
if ref_prices:
ref_price = ref_prices[0][0]
price = ref_prices[0][1]
if ref_prices[0][0] == ref_prices[0][1]:
price = ''
ref_price = ref_prices[0][1]
r1 = s.get(info_url)
comment_num, comment_types = self.getcomment(s, infos[0])
self.insertmysql(info_id, info_url, price, ref_price, name, comment_num, comment_types)
# 每个手机的多种搭配方式
colorsize = re.findall(r'colorSize: \[(.*?)\]', r1.text, re.S)
if colorsize:
skuIds = re.findall(r'"skuId":(\d+),', colorsize[0], re.S)
for skuId in skuIds:
print(skuId)
skuid_url = 'https://item.jd.com/{}.html'.format(skuId)
r4 = s.get(skuid_url)
# 每种类型的标题
skuid_name = re.findall(r'<div class="sku-name">(.*?)</div>', r4.text, re.S)
if not skuid_name:
continue
skuid_name = self.name_tool(str(skuid_name[0])).strip()
r5 = s.get(self.phone_price.format(skuId))
# 每种类型的价格
skuid_prices = re.findall(r'"jdPrice":{.*?"op":"(.*?)".*?"p":"(.*?)".*?}', r5.text, re.S)
skuid_price, ref_skuid_price = '', ''
if skuid_prices:
ref_skuid_price = skuid_prices[0][0]
skuid_price = skuid_prices[0][1]
if skuid_prices[0][0] == skuid_prices[0][1]:
skuid_price = ''
ref_skuid_price = skuid_prices[0][1]
skuid_comment_num, skuid_comment_types = self.getcomment(s, skuId)
self.insertmysql(skuId, skuid_url, skuid_price, ref_skuid_price, skuid_name, skuid_comment_num, skuid_comment_types)
以及这里处理评论数 ,名称的工具方法
@staticmethod
def price_tool(prices):
price = re.findall(r'<i>(.*?)</i>', prices, re.S)[0]
if 'data-price' in prices:
price = re.findall(r'data-price="(.*?)">', prices, re.S)[0]
return price
@staticmethod
def name_tool(name):
name = re.sub(r'<.*?>', '', name)
return name
def getcomment(self, s, ids):
# 好评数
r2 = s.get(self.good_url.format(ids))
# 好评类型
# r3 = s.get(self.comments_type.format(ids), proxies=self.proxies)
comment_num = re.findall(r'"CommentCount":(\d+),', r2.text, re.S)
if comment_num:
comment_num = comment_num[0]
# comment_type = re.findall(r'\"hotCommentTagStatistics\":\[(.*?)\]', r3.text, re.S)
comment_types = ''
# if comment_type and len(comment_type):
# comment_types_list2 = re.findall(r'{.*?"name":"(.*?)".*?"count":(\d+),.*?}', comment_type[0], re.S)
# for c in comment_types_list2:
# comment_types += (c[0] + "(" + str(c[1]) + ") ")
return comment_num, comment_types
结果.avi
print('微信公众号搜索 "猿狮的单身日常" ,Java技术升级、虫师修炼,我们 不见不散!')
print('也可以扫下方二维码哦~')
猿狮的单身日常












网友评论