抓取 JD 商品
先说说起因吧,是因为有朋友找我一起合作抓取某东的商品数据,我做为一个刚入爬虫的新手,当然是不可能完整的拿下这个啦.这次爬虫要的是商品的详细数据,我的工作就是筛选所有的商品的 url,解析成 json 文件,传给他,他在继续通过我传入的 url 进行商品的详细信息
需求
这次的需求是通过关键字,找出含有关键字信息的产品,并且按照高级筛选的条件,要前 100 条商品的数据,如下
还要根据销量,价格,评论数再来分别通过高级筛选来得到商品的 url
这个就是我要完成的部分了
思路
在浏览器输入关键字某某某某,进入到页面
2. 分析页面源码,如何能抓取我们要的高级筛选的属性
3. 抓取筛选页的前 100 个商品 url
代码实现+思路讲解
得到高级筛选的字段
在这里我们能看到,防水等级就是我们要的高级筛选的字段,但是其实还有一些问题吗,这里只给了我们防水等级并没有进入到具体的分类
比如在防水等级里就有两个分类 IPX7 和不防水,我们就需要继续在页面内找,这两个字段在哪里,这时候就可以使用 control+f 来查找了
"""
第一页
https://search.jd.com/search?keyword=%E6%99%BA%E8%83%BD%E9%99%AA%E4%BC%B4&ev=2342_80416%5E
https://search.jd.com/search?keyword=%E6%99%BA%E8%83%BD%E9%99%AA%E4%BC%B4&ev=2342_10097%5E
"""
import json
import re
import requests
from lxml import etree
"""
第二页
https://search.jd.com/search?keyword=%E6%99%BA%E8%83%BD%E9%99%AA%E4%BC%B4&&ev=2342_80416%5E&page=3&s=61&click=0
"""
headers={
'cookie': 'jsavif=1; jsavif=1; __jda=122270672.1676954628911215392914.1676954629.1676954629.1676954629.1; __jdc=122270672; __jdv=122270672|direct|-|none|-|1676954628913; shshshfp=ed3eddd61f715d1c96c9f6c16fbe0b4d; shshshfpa=ad1721fc-fb58-e043-0884-f3e3ec7ecbfa-1676954631; shshshfpx=ad1721fc-fb58-e043-0884-f3e3ec7ecbfa-1676954631; rkv=1.0; shshshfpb=m7YIEJ4kE_I09iQUOqucbNw; __jdu=1676954628911215392914; avif=1; areaId=9; ipLoc-djd=9-687-0-0; wlfstk_smdl=yc99d5v35ijyw20zsopjutf8cepak91i; TrackID=1kdV6GF1RffnRbe0Qp0GpJB-SZpgdYbiwt7b0CEM2f-Qes4EHmzCguXvIxhLu0kyjLvRIBLlXSZowydOT8eGqpkOTUI-UdWXbh2BcVDzNdqI; pinId=BuFXDWEgSjOpfSLAe_c1dA; pin=jd_oLZlDnoADreu; unick=jd_oLZlDnoADreu; ceshi3.com=000; _tp=U7c%2B2nqfBmVC0%2FrGlXCSJw%3D%3D; _pst=jd_oLZlDnoADreu; qrsc=3; thor=2BFA8AF79CCDF02724AC400C4157CF8EBB31A9583271110F6E6D8E13410D78445553C630236F92E98969BCD22A8D8CB0C41302FC31CBE0A2317A9EC1ED0F91781CB9DF3D61A97C3F80AE2DE488F8C11C8A37877C14B238CC6C714C521F3D4B63F6D6E8CC26676776A9C222D368AB117C9DE11182B5067CA10404624893CF29452F53873DE232D1A749D694343D18A5C949A24608AB468A71576F0B45B2F1B5C6; __jdb=122270672.7.1676954628911215392914|1.1676954629; shshshsID=c9fb7fbdf834fc947f6247315a300911_5_1676955986077; 3AB9D23F7A4B3C9B=MH3KQE7TQIQI2EQYBOGYVWJVC4DHHDSOZY6GKNBSQ3Z3X5TGIDW7VHWUBMM3KQRB73QFJDNYEOKG6ZG7HSG4VXAKX4',
'referer': 'https://www.jd.com/',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.50'
}
def select_url():
url='https://search.jd.com/search?keyword=%E6%99%BA%E8%83%BD%E9%99%AA%E4%BC%B4&psort=4&psort=4&pvid=945b3a4408f84c7ea93a498ea22afa8e&click=1'
# 先抓取高级搜索的每个url
# 获取页面源码
response = requests.get(url, headers=headers)
tree = etree.HTML(response.content.decode('utf-8'))
# 高级搜索中的每个url
# //ul[@class="menu"]/li[position()>1 and position()<5]
# 找到每一个ul下载li
li_list = tree.xpath(
'//div[@class="sl-tab-cont"]/div[position()>0 and position()<10]/div[@class="sl-v-list"]/ul/li')
items={}
item_list=[]
for li in li_list:
item={}
broken_url = li.xpath('./a/@href')
broken_name = li.xpath('./a/@onclick')
ev = str.split(broken_url[0], '&')[-1]
print(broken_name)
# 使用re来对
name = ''.join(re.findall("searchlog(.*?,'(.*?)')", ''.join(broken_name)))
item['ev']=ev
item['name']=name
item_list.append(item)
items['data'] = item_list
with open('./高级筛选/综合-高级筛选.json','w',encoding='utf-8')as f:
f.write(json.dumps(items,ensure_ascii=False))
if __name__ == '__main__':
select_url()
复制代码
这里需要注意一点,我们页面上显示的只有 10 个分类,所有 div 也只需要抓 10 就可以了,这时候 div[position()>0 and position()<10]只需要这样写就可以了,跟切片的方法类似
li_list = tree.xpath(
'//div[@class="sl-tab-cont"]/div[position()>0 and position()<10]/div[@class="sl-v-list"]/ul/li')
复制代码
最后进行 json 文件的生成
结果
这里的 url 我没什么要用 ev 来截取出来,因为在后期的时候我们通过分析每一个详情页的时候,需要拼接 url,这里我先跟大家说明一下,看到后面大家就理解了
因为只需要三种类型综合,销量,评论数,后续的 url 会用到,所以我就手动的拼接进 json 的文件,为了后续的拼接整体的 url
综合 Top100 商品的 url 形成 json 文件
在这里我们就需要拼接 url 了
https://search.jd.com/search?keyword=%E6%99%BA%E8%83%BD%E9%99%AA%E4%BC%B4&ev=1394_117993%5E&pvid=92c6f4ae1ed54163b1c3e7e7af738231&click=0&page=1&s=1
https://search.jd.com/search?keyword=%E6%99%BA%E8%83%BD%E9%99%AA%E4%BC%B4&ev=1394_117993%5E&pvid=92c6f4ae1ed54163b1c3e7e7af738231&psort=3&click=0&page=3&s=61
keyword 就是我们输入的关键字
ev 就是我们高级筛选出来的字段
pvid 就是我们的根据销量筛选出来的
psort 这个变量是根据销量和评论数得到的跟随变量
page 这个是页面
s 也是一个变量
import json
import os
import time
from selenium.webdriver.common.by import By
from selenium import webdriver
file_list = os.listdir('排名_json')
options = webdriver.ChromeOptions()
options.add_experimental_option('excludeSwitches', ['enable-automation'])
options.add_argument('--disable-blink-features=AutomationControlled')
# options.add_argument('--proxy-server=http://代理服务器:端口')
driver = webdriver.Chrome(options=options)
driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
"source": '''
Object.defineProperty(navigator,'webdriver',{
get:()=>undefined
})
'''
})
headers = {
'cookie': 'jsavif=1; jsavif=1; __jda=122270672.1676954628911215392914.1676954629.1676954629.1676954629.1; __jdc=122270672; __jdv=122270672|direct|-|none|-|1676954628913; shshshfp=ed3eddd61f715d1c96c9f6c16fbe0b4d; shshshfpa=ad1721fc-fb58-e043-0884-f3e3ec7ecbfa-1676954631; shshshfpx=ad1721fc-fb58-e043-0884-f3e3ec7ecbfa-1676954631; rkv=1.0; shshshfpb=m7YIEJ4kE_I09iQUOqucbNw; __jdu=1676954628911215392914; avif=1; areaId=9; ipLoc-djd=9-687-0-0; wlfstk_smdl=yc99d5v35ijyw20zsopjutf8cepak91i; TrackID=1kdV6GF1RffnRbe0Qp0GpJB-SZpgdYbiwt7b0CEM2f-Qes4EHmzCguXvIxhLu0kyjLvRIBLlXSZowydOT8eGqpkOTUI-UdWXbh2BcVDzNdqI; pinId=BuFXDWEgSjOpfSLAe_c1dA; pin=jd_oLZlDnoADreu; unick=jd_oLZlDnoADreu; ceshi3.com=000; _tp=U7c%2B2nqfBmVC0%2FrGlXCSJw%3D%3D; _pst=jd_oLZlDnoADreu; qrsc=3; thor=2BFA8AF79CCDF02724AC400C4157CF8EBB31A9583271110F6E6D8E13410D78445553C630236F92E98969BCD22A8D8CB0C41302FC31CBE0A2317A9EC1ED0F91781CB9DF3D61A97C3F80AE2DE488F8C11C8A37877C14B238CC6C714C521F3D4B63F6D6E8CC26676776A9C222D368AB117C9DE11182B5067CA10404624893CF29452F53873DE232D1A749D694343D18A5C949A24608AB468A71576F0B45B2F1B5C6; __jdb=122270672.7.1676954628911215392914|1.1676954629; shshshsID=c9fb7fbdf834fc947f6247315a300911_5_1676955986077; 3AB9D23F7A4B3C9B=MH3KQE7TQIQI2EQYBOGYVWJVC4DHHDSOZY6GKNBSQ3Z3X5TGIDW7VHWUBMM3KQRB73QFJDNYEOKG6ZG7HSG4VXAKX4',
'referer': 'https://www.jd.com/',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.50'
}
def get_good_url(url, ev_name, pvid_name, page):
file = f'{pvid_name}_{ev_name}_{page}.json'
if file in file_list:
return True
items = {}
item_list = []
count = 0 if page == 1 else 60
driver.get(url) # 遍历每个列表链接
time.sleep(1)
buffer() # 缓冲,使页面加载完整
number = int(driver.find_element(By.XPATH, '//div[@id="J_topPage"]/span/i').text)
info = driver.find_elements(By.CLASS_NAME, 'gl-i-wrap') # 寻找商品链接的父阶
for data in info:
item = {}
# 获取商品列表页所有的商品链接
good_url = data.find_element(By.CLASS_NAME, 'p-img').find_element(By.TAG_NAME, 'a').get_attribute(
'href')
good_price = data.find_element(By.CLASS_NAME, 'p-price').find_element(By.TAG_NAME,
'strong').find_element(
By.TAG_NAME, 'i').text
good_name = data.find_element(By.CLASS_NAME, 'p-name').find_element(By.TAG_NAME,
'a').find_element(
By.TAG_NAME, 'em').text
count += 1
print('正在爬取' + pvid_name + ev_name + '第' + str(page - 1) + '页的数据')
item['good_url'] = good_url
item['good_name'] = good_name
item['good_price'] = good_price
item['good_num'] = count
item_list.append(item)
if count == 100:
break
items['goods_ingo'] = item_list
with open(f'排名_json/{pvid_name}_{ev_name}_{page}.json', 'w', encoding='utf-8')as f:
f.write(json.dumps(items, ensure_ascii=False))
time.sleep(2)
return True if number > 1 else False
def get_good_json():
"""
得到以综合排序的url
:return: 返回一个列表,列表里第一个集合是所有第一页的url,第二集合是第二页的url,最后相加
"""
keyword = '%E6%99%BA%E8%83%BD%E9%99%AA%E4%BC%B4'
# 综合高级筛选
with open('./高级筛选/综合-高级筛选.json', 'r', encoding='utf-8')as f:
content = f.read()
data = json.loads(content)
# print(type(data))
# print(data)
ev_name = data['data']
pvid = data['pvid']
# print(ev_name)
pages = [1, 3]
for item in ev_name:
ev = item['ev']
evName = item['name']
pvid_name = data['pvid_name']
# 所有高级筛选的
# 第一页数据
page1 = pages[0]
url1 = f'https://search.jd.com/search?keyword={keyword}&{ev}&page={page1}&s=1&click=0&pvid={pvid}'
index = get_good_url(url1, evName, pvid_name, page1)
if index:
# 第二页数据
page2 = pages[1]
url2 = f'https://search.jd.com/search?keyword={keyword}&{ev}&page={page2}&s=61&click=0&pvid={pvid}'
get_good_url(url2, evName, pvid_name, page2)
def buffer():
"""
定义函数缓慢拖拽页面,使其加载完成
:return:
"""
for i in range(25):
time.sleep(0.6)
driver.execute_script('window.scrollBy(0,500)', '')
if __name__ == '__main__':
get_good_json()
复制代码
这里为什么定义了一个 buffer 函数,这个是因为当我们在滑动网页的时候只显示前 30 条商品的数据,只有在滑动条到达某一个临界值的时候,才会加载后面的数据,这个叫做懒加载机制,这样网站是为了节省流量,所以我采用的是 selenium 进行一个 dom 操作进行网页的下滑,这里还需要注意的一点就是我们需要进行一个睡眠操作,不然就会因为滑动过快导致页面资源加载不出来,就会报错.
def buffer():
"""
定义函数缓慢拖拽页面,使其加载完成
:return:
"""
for i in range(25):
time.sleep(0.6)
driver.execute_script('window.scrollBy(0,500)', '')
复制代码
这里我们我进行了一个骚操作,因为客户要的只是前两页 100 条的数据,所以有些就固定写死就可以了,后期我们还要分页第一页第二页,这个数据也是要作为函数的变量传进去,后续会体现到这个参数的作用
with open('./高级筛选/综合-高级筛选.json', 'r', encoding='utf-8')as f:
content = f.read()
data = json.loads(content)
# print(type(data))
# print(data)
ev_name = data['data']
pvid = data['pvid']
# print(ev_name)
pages = [1, 3]
for item in ev_name:
ev = item['ev']
evName = item['name']
pvid_name = data['pvid_name']
# 所有高级筛选的
# 第一页数据
page1 = pages[0]
url1 = f'https://search.jd.com/search?keyword={keyword}&{ev}&page={page1}&s=1&click=0&pvid={pvid}'
index = get_good_url(url1, evName, pvid_name, page1)
if index:
# 第二页数据
page2 = pages[1]
url2 = f'https://search.jd.com/search?keyword={keyword}&{ev}&page={page2}&s=61&click=0&pvid={pvid}'
get_good_url(url2, evName, pvid_name, page2)
复制代码
进行每一页商品的查询
这里的逻辑是这样的,如果有第二页就说明能继续爬第二页的数据,并且定义一个计数器,这个计数器的作用是记录第一页的 60 条数据,并且把这个数据作为第二页的起始数据,查到第 100 条就结束,如果没有第二页的数据,就正常的查,并且录入
def get_good_url(url, ev_name, pvid_name, page):
file = f'{pvid_name}_{ev_name}_{page}.json'
if file in file_list:
return True
items = {}
item_list = []
count = 0 if page == 1 else 60
driver.get(url) # 遍历每个列表链接
time.sleep(1)
buffer() # 缓冲,使页面加载完整
number = int(driver.find_element(By.XPATH, '//div[@id="J_topPage"]/span/i').text)
info = driver.find_elements(By.CLASS_NAME, 'gl-i-wrap') # 寻找商品链接的父阶
for data in info:
item = {}
# 获取商品列表页所有的商品链接
good_url = data.find_element(By.CLASS_NAME, 'p-img').find_element(By.TAG_NAME, 'a').get_attribute(
'href')
good_price = data.find_element(By.CLASS_NAME, 'p-price').find_element(By.TAG_NAME,
'strong').find_element(
By.TAG_NAME, 'i').text
good_name = data.find_element(By.CLASS_NAME, 'p-name').find_element(By.TAG_NAME,
'a').find_element(
By.TAG_NAME, 'em').text
count += 1
print('正在爬取' + pvid_name + ev_name + '第' + str(page - 1) + '页的数据')
item['good_url'] = good_url
item['good_name'] = good_name
item['good_price'] = good_price
item['good_num'] = count
item_list.append(item)
if count == 100:
break
items['goods_ingo'] = item_list
with open(f'排名_json/{pvid_name}_{ev_name}_{page}.json', 'w', encoding='utf-8')as f:
f.write(json.dumps(items, ensure_ascii=False))
time.sleep(2)
return True if number > 1 else False
复制代码
总结
我们在使用 selenium 进行元素定位的时候,比如
info = driver.find_elements(By.CLASS_NAME, 'gl-i-wrap') # 寻找商品链接的父阶
这里我们使用的并不是 By.XPATH,如果是在这个节点之下使用的 By.XPATH,这是会报错,说他的类型不匹配.我们就还是需要使用 find_element(By.CLASS_NAME,来清洗数据
还有一点,就是如果说我们在把数据保存在本地的时候,如果说有一处报错,那么这出报错我们也不知道在哪里,也不知道这个数据有没有抓取成功
def get_good_url(url, ev_name, pvid_name, page):
file = f'{pvid_name}_{ev_name}_{page}.json'
if file in file_list:
return True
复制代码
这个我们就是读取的文件名,如果这个文件名存在就说明我们能爬的到这个一个类型的 json 文件,就直接返回 true,因为这个函数在另一个函数的 for 循环内,就说明跳出了这个循环,虽然这个方法也是有点慢,需要一个个检索文件名,但是好处就是我们不会重复的读取网页中每一个内容,在一定程度上,我们把代码的运行速度变快了.
评论