1. 业务需求

最近想通过爬虫抓取某电商商品页的商品详情,
浏览器页面打开如下:

本来以为是一个很简单的爬虫,却没想到一波三折,并没有那么简单。

2. 付诸实践

接到任务后,就兴冲冲的写了段代码来爬取网页数据。

# 厨房卫浴
href = 'http://search.gome.com.cn/search?question=%E5%8E%A8%E6%88%BF%E5%8D%AB%E6%B5%B4'

res = requests.get(href)
# print(res.text)
soup = BeautifulSoup(res.text, 'html.parser')
# product_list = soup.find('li', class_='product-item hideSmallBox')
# print(product_list)

product_list = soup.find_all('li', attrs={'class': 'product-item hideSmallBox'})
for item in product_list:
    if type(item) is bs4.element.Tag:
        print(item)
        sku_id = item.get('skuid')
        """
         <a class="emcodeItem item-link" href="//item.gome.com.cn/9140257462-1130961371.html?search_id=sdw233vyv434" target="_blank"
         title="宜来卫浴厨房龙头E-49018镀铬" 
        track="产品列表图片"><img alt="宜来卫浴厨房龙头E-49018镀铬" gome-src="//gfs17.gomein.net.cn/T1hK_eBj_T1RCvBVdK_210.jpg" 
        src="//app.gomein.net.cn/images/grey.gif"/></a>
        """
        emcode_item = item.find('a', class_='emcodeItem item-link')
        item_title = emcode_item.attrs['title']
        item_url = emcode_item.attrs['href']
        """
        <img alt="宜来卫浴厨房龙头E-49018镀铬" gome-src="//gfs17.gomein.net.cn/T1hK_eBj_T1RCvBVdK_210.jpg" 
        src="//app.gomein.net.cn/images/grey.gif"/>
        """
        item_image_tag = emcode_item.find('img')
        item_image_url = item_image_tag.attrs['gome-src']

执行任务后,好像哪里不对,遇到了意想不到的问题。

3. 出现问题

问题来了,查看网页源代码是能看到价格的,如下:

但是我通过BeautifulSoup解析后却死活找不到价格在那里。如下图,本应出现在price的<span>标签中的价格呢?

后来检查了一通,才知道该电商页面的价格是通过js加载的,这也是一般电商行业防爬虫的手段之一,也就是说直接获取html的<span>是不能的。

4. 找出问题

F12 打开这个页面,按住Ctrl + F 调出左侧搜索框,搜索价格:

经过一通排查,终于发现了猫腻,原来价格获取藏在这里,接下来又是一顿操作终于找到藏在深处的获取价格的url,如下图:

把这个链接拿出来:
http://ss.gome.com.cn/search/v1/price/single/1001/G001/9140257462/1130961371/11010000/flag/item/fn1616072940509?callback=fn1616072940509&_=1616072940509

这个就是该电商商品页的价格获取链接,然后再来看看响应吧:

fn1616072940509({
  "success": true,
  "result": {
    "price": "299.0",
    "priceType": "RUSHBUYPRICE",
    "productId": "9140257462",
    "skuId": "1130961371"
  }
})

我们解析一下上面的url,9140257462是productId,1130961371是skuId,这两个值都可以从HTML中获取到,然后再研究一下这个链接,发现还有三个参数未知:

经过N次的摸索测试,发现这三个参数如果不知道就都写成null就好了,这应该是该电商的一个漏洞吧,而且按照下面的连接请求,也能获取到价格:

http://ss.gome.com.cn/search/v1/price/single/null/null/9140257462/1130961371/null/flag/item

截止到此,已经探索出来商品的价格获取链接了,然后又赶紧改进代码,增加一个获取价格的方法:

def get_item_price(product_id, skuid):
    """
    :param product_id: 商品id
    :param skuid: skuId
    :return: 返回价格
    """
    price_url = 'http://ss.gome.com.cn/search/v1/price/single/null/null/' + product_id + '/' + skuid + '/null/flag/item'
    headers = {
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
                      '(KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36'
    }
    response = requests.get(price_url, headers=headers, timeout=30)
    if response.status_code == 200:
        return response.json()['result']['price']
    else:
        return "价格未知"

5. 最终代码

找到问题所在之后,确定最终代码:

# encoding: utf-8

import bs4
import requests
from bs4 import BeautifulSoup
import csv
import os
import platform

# 全局变量 用来计算一共爬虫爬了多少条数据
counter = 0
file = ''
project = ''


def write_fo_file(file_path, rows_data, *is_touch_file):
    """
    :param file_path:
    :param rows:
    :param is_touch_file:
    :return:
    """
    if is_touch_file and is_touch_file[0] == 1:
        # sys_str = platform.system()
        # if sys_str == "Windows":
        #     if not os.path.exists(file_path):
        #         os.system(r"type nul > {}".format(file_path))
        #     else:
        #         print(file_path, '文件已存在')
        # elif sys_str == "Linux":
        #     if not os.path.exists(file_path):
        #         os.system(r"touch {}".format(file_path))
        #     else:
        #         print(file_path, '文件已存在')
        # else:
        #     print("Other System tasks: %s" % sys_str)
        global file
        file = file_path
        with open(file_path, "a", newline="", encoding='utf-8-sig') as csvfile:
            rows = ("商品名称", "商品productID", "商品sku_id", '商品链接', '商品图片', '商品价格')
            writer = csv.writer(csvfile)
            writer.writerow(rows)
            csvfile.flush()
            csvfile.close()
    else:
        with open(file_path, "a", newline="", encoding='utf-8-sig') as csvfile:
            writer = csv.writer(csvfile)
            # 批量写入文件
            for row_data in rows_data:
                writer.writerow(row_data)
            csvfile.flush()
            csvfile.close()


def get_product_lists(product_lists):
    item_lists = []
    for item in product_lists:
        if type(item) is bs4.element.Tag:
            # print(item)
            sku_id = item.get('skuid')
            pid = item.get('pid')

            """
            样例数据:
             <a class="emcodeItem item-link" href="//item.gome.com.cn/9140257462-1130961371.html?search_id=sdw233vyv434" target="_blank"
             title="宜来卫浴厨房龙头E-49018镀铬" 
            track="产品列表图片"><img alt="宜来卫浴厨房龙头E-49018镀铬" gome-src="//gfs17.gomein.net.cn/T1hK_eBj_T1RCvBVdK_210.jpg" 
            src="//app.gomein.net.cn/images/grey.gif"/></a>
            """
            emcode_item = item.find('a', class_='emcodeItem item-link')
            item_title = emcode_item.attrs['title']
            item_url = emcode_item.attrs['href']
            """
            样例数据:
            <img alt="宜来卫浴厨房龙头E-49018镀铬" gome-src="//gfs17.gomein.net.cn/T1hK_eBj_T1RCvBVdK_210.jpg" 
            src="//app.gomein.net.cn/images/grey.gif"/>
            """
            item_image_tag = emcode_item.find('img')
            item_image_url = item_image_tag.attrs['gome-src']
            item_price = get_item_price(product_id=pid, skuid=sku_id)
            info = {'name': item_title, 'pid': pid, 'sku_id': sku_id, "href": item_url,
                    'image': item_image_url, 'price': item_price}
            item_info = (item_title, pid, sku_id, 'http:' + item_url, 'http:' + item_image_url, item_price)
            item_lists.append(item_info)
    return item_lists


def get_page(url, page_num):
    """
    :param url: 要爬取页面的url
    :param page_num: 要爬取的页数
    :return:
    """
    headers = {
        "Accept": "application/json, text/javascript, */*; q=0.01",
        "Host": "search.gome.com.cn",
        "Referer": "http://search.gome.com.cn/search?question=%E5%8E%A8%E6%88%BF%E5%8D%AB%E6%B5%B4",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36",
        'X-Requested-With': 'XMLHttpRequest'
    }

    """
    Chengyanan 注: 
        如果为了更好的展示数据效果,可将下列type 改为json即可
        我这里是为了更好的使用BeautifulSoup解析,所以用了默认的HTML类型
    """
    for i in range(1, page_num + 1):
        formdata = {
            "facets": '',
            "page": i,
            # "type": "json",
            "aCnt": 0
        }
        # print([url, formdata])
        try:
            r = requests.post(url, data=formdata, headers=headers)
            soup = BeautifulSoup(r.text, 'html.parser')
            product_list = soup.find('ul', class_='product-lists clearfix')
            items = get_product_lists(product_list)
            global counter
            counter += items.__len__()
            print(r"当前正在爬取的项目是{},一共需要爬取{}页,正在爬取第{}页,本页爬取数据{}条,累计爬取{}条..."
                  .format(project, page_num, i, items.__len__(), counter))
            write_fo_file(file, items)
        except Exception as e:
            print(e)
            print('链接失败')


def get_item_price(product_id, skuid):
    """
    :desc 根据输入的产品id 和 skuid 获取商品的价格
    :param product_id: 商品id
    :param skuid: skuId
    :return: 返回价格
    """
    price_url = 'http://ss.gome.com.cn/search/v1/price/single/null/null/' + product_id + '/' + skuid + '/null/flag/item'
    headers = {
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
                      '(KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36'
    }
    response = requests.get(price_url, headers=headers, timeout=30)
    if response.status_code == 200:
        return response.json()['result']['price']
    else:
        return "价格未知"


if __name__ == '__main__':

    # 家居家装下分类品牌
    dicts = {
        "厨房卫浴": 'chu_fang',
        "灯饰照明": 'deng_shi',
        # "五金电料": 'wujin',
        # "装修材料": 'zhuangxiu',
        "客厅家具": 'keting',
        "卧室家具": 'woshi',
        "储物家具": 'chuwu',
        "办公家具": 'bangong'
    }
    for i in dicts.items():
        project = i[0]
        write_fo_file(r'gm_spider_{}.csv'.format(i[1]), '', 1)
        get_page(r'http://search.gome.com.cn/search?question={}'.format(i[0]), 4)

爬取结果


关注一下:
在这里插入图片描述

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐