写点什么

Spider 实战系列 - 一次真实接单经历让我抓取了某东的数据

作者:浅辄
  • 2023-04-17
    吉林
  • 本文字数:7295 字

    阅读完需:约 24 分钟

Spider实战系列-一次真实接单经历让我抓取了某东的数据

抓取 JD 商品

先说说起因吧,是因为有朋友找我一起合作抓取某东的商品数据,我做为一个刚入爬虫的新手,当然是不可能完整的拿下这个啦.这次爬虫要的是商品的详细数据,我的工作就是筛选所有的商品的 url,解析成 json 文件,传给他,他在继续通过我传入的 url 进行商品的详细信息

需求

这次的需求是通过关键字,找出含有关键字信息的产品,并且按照高级筛选的条件,要前 100 条商品的数据,如下



还要根据销量,价格,评论数再来分别通过高级筛选来得到商品的 url


这个就是我要完成的部分了

思路

  1. 在浏览器输入关键字某某某某,进入到页面



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%5Ehttps://search.jd.com/search?keyword=%E6%99%BA%E8%83%BD%E9%99%AA%E4%BC%B4&ev=2342_10097%5E
"""import jsonimport re
import requestsfrom 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 jsonimport osimport timefrom selenium.webdriver.common.by import Byfrom 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 循环内,就说明跳出了这个循环,虽然这个方法也是有点慢,需要一个个检索文件名,但是好处就是我们不会重复的读取网页中每一个内容,在一定程度上,我们把代码的运行速度变快了.

发布于: 刚刚阅读数: 3
用户头像

浅辄

关注

大丈夫生于天地之间,岂能郁郁久居人之下 2022-11-08 加入

Java、Spider、Go

评论

发布
暂无评论
Spider实战系列-一次真实接单经历让我抓取了某东的数据_爬虫_浅辄_InfoQ写作社区