likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 15

我正在参加「掘金·启航计划」

抓取JD商品

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

需求

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

Spider实战系列-一次真实接单经历让我抓取了某东的数据 还要根据销量,价格,评论数再来分别通过高级筛选来得到商品的url

Spider实战系列-一次真实接单经历让我抓取了某东的数据 这个就是我要完成的部分了

思路

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

Spider实战系列-一次真实接单经历让我抓取了某东的数据 2. 分析页面源码,如何能抓取我们要的高级筛选的属性

Spider实战系列-一次真实接单经历让我抓取了某东的数据 3. 抓取筛选页的前100个商品url

代码实现+思路讲解

得到高级筛选的字段

Spider实战系列-一次真实接单经历让我抓取了某东的数据 在这里我们能看到,防水等级就是我们要的高级筛选的字段,但是其实还有一些问题吗,这里只给了我们防水等级并没有进入到具体的分类

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

比如在防水等级里就有两个分类IPX7和不防水,我们就需要继续在页面内找,这两个字段在哪里,这时候就可以使用control+f来查找了

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

"""

第一页
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,这里我先跟大家说明一下,看到后面大家就理解了

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

因为只需要三种类型综合,销量,评论数,后续的url会用到,所以我就手动的拼接进json的文件,为了后续的拼接整体的url Spider实战系列-一次真实接单经历让我抓取了某东的数据

综合Top100商品的url形成json文件

在这里我们就需要拼接url了

search.jd.com/search?keyw…

search.jd.com/search?keyw…

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

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

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

总结

我们在使用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循环内,就说明跳出了这个循环,虽然这个方法也是有点慢,需要一个个检索文件名,但是好处就是我们不会重复的读取网页中每一个内容,在一定程度上,我们把代码的运行速度变快了.