从零开始写Python爬虫 --- 爬虫应用:一号店 商品信息查询程序

从零开始写Python爬虫 --- 爬虫应用:一号店 商品信息查询程序

这次我们来爬取电商信息,还是那句话,爬虫抓下来的数据一定要有意义才行,
至于为什么要抓电商,我才不会告诉你我想抢一部魅蓝note6呢

成果展示:

这次相当于将爬虫写成了一个随时可以用的小脚本,
先来看一下最终的结果:



思路分析

先来看一下项目结构

.
├── __pycache__
│   ├── citydict.cpython-36.pyc
│   └── spider.cpython-36.pyc
├── citydict.py # 静态资源文件 城市id 省份id
├── cityid.html # 用于解析城市id的一号店网页部分内容
├── handler.py # 程序的主要入口<提供基础的用户交互>
├── spider.py   # 爬虫部分
└── tools.py     # 解析数据的部分
1 directory, 7 files

经过了这么长时间的写爬虫经验
我们对一些网站的基本数据分布也有了自己的感觉
就比如说电商的数据
一般情况下都是ajax动态生成的
所以想要直接用requests抓取页面解析
是行不通的,我们来试试看

打开一个一号店商品的页面:
item.yhd.com/item/76716

打开chrome开发者工具


发现我们想要的数据
果然是通过ajax的方式渲染到页面上的
有很多啦:
* 价格
* 是否可以销售
* 是否显示
* 库存
* 本地库存
* 销售数量
* ....

一号店鸡贼的地方在于:
他居然不把数据放在chr界面,
而是放在了一个动态的js文件中
可叫我一顿好找

来分析一下这个ajax请求



这是一个GET下来的文本数据(居然不用json)
请求头中我们需要注意的就是:

  • request url
  • 请求参数
mcsite:1 # 必填参数
provinceId:3 # 省份id
cityId:23 # 城市id
countyId:252 # 国家id
pmId:76716479 # 商品id
ruleType:2 # 商品规则
businessTagId:16 # 还没弄清楚
callback:jQuery111305397697550805665_1504256542202 # 回掉参数 大概是unix时间戳的某种变形
_:1504256542203

到这里我们的思路就清楚了:

  • 构造符合要求的request url
  • 解析数据
经过我的尝试,我发现最后的回调参数是可以不清求的,估计一号店那里还没有开启相关的验证

请求参数的获取:

pmId: 我们很容易就能从url链接里获取
provinceId、cityId :这两个明显是一个静态的资源,
我们只需要找到提供对应关系的字典/json文件
就能找到对应的关系了

可是我在资源文件里找了好久,
都没有找到相关的文件
这让我有点灰心啊,
后来一想,反转这个文件只需要解析一次
我直接从网页的数据里解析不也一样嘛?
于是乎我就截取了部分一号店首页的html源码
解析出了我想要的数据


我们来整理一下思路

  • 接受用户输入的商品名
  • 通过搜索解析出商品id
  • 通过本地文件的城市省份id表构造ajax请求
  • 解析ajax返回的数据并展示

代码展示

城市省份id解析部分

'''
解析1号店的所有
省份
城市信息
'''
import os
from bs4 import BeautifulSoup

# 获取当前运行目录
path = os.path.dirname(os.path.abspath(__file__))

with open(path + '/cityid.html') as f:
    html = f.read()

def get_cityid_map(html):
    '''
    解析一号店省份、城市id
    return <dict>
    '''
    cityid_map = {}
    soup = BeautifulSoup(html, 'lxml')
    # 找到所有的a标签
    citys = soup.find_all('a')
    # 开始解析城市名城市id 省份id
    for city in citys:
        name = city.text.replace('市','')
        provinceId = city['data-provinceid']
        cityid = city['data-cityid']
        cityid_map[name] = {'provinceId': provinceId, 'cityid': cityid, }

    return cityid_map

关键的爬虫部分

import requests
from bs4 import BeautifulSoup


def get_html_text(url):
    '''
    返回网页text
    '''
    try:
        r = requests.get(url, timeout=30)
        r.raise_for_status()
        r.encoding = r.apparent_encoding
        return r.text
    except:
        raise ValueError('errors')


def parse_good_detail(pmId, provinceId=5, cityid=37):
    '''
    查询指定id商品的库存和价格
    默认查询 江苏省 南京市 的库存
    '''
    # 一号点的Ajax服务器请求地址
    # 默认使用江苏省为省份信息
    url = 'http://gps.yhd.com/restful/detail?mcsite=1&provinceId={}&cityId={}&pmId={}&ruleType=2&businessTagId=16'.format(
        provinceId, cityid, pmId)
    text = get_html_text(url)
    # 对信息进行初步格式化 删掉data无用信息
    content = text[text.find('{') + 1:-2]
    data_dict = {}
    # 将所有的类json数据格式化存入字典
    for rec in content.split(','):
        data_dict[rec.split(":")[0].replace(
            '"', '').replace('"', '')] = rec.split(':')[1]

    # 查找我们想要的信息
    price = data_dict['currentPrice']
    stock = data_dict['currentStockNum']

    return price, stock


def parse_goods_info(url,provinceId=5, cityid=37):
    '''
    抓取指定url的所有商品的

    商品id
    价格
    库存
    链接

    returen: goods_infolist<dict in list>
    '''

    goods_infolist = []

    html = get_html_text(url)
    soup = BeautifulSoup(html, 'lxml')
    # 找到所有商品的a标签
    goods_list = soup.find_all('a', class_='mainTitle')

    for good in goods_list:
        url = good['href'][2:]
        title = ''.join(good['title'].split(' ')[:3])  # 对标题稍微格式化一下
        pmId = good['pmid']
        try:
            price, stock = parse_good_detail(pmId,provinceId,cityid)
        except:
            price, stock = '信息错误', '信息错误'
        goods_infolist.append(
            {'name': title, 'price': price, 'stock': stock, 'url': url})

    return goods_infolist

用户交互的部分

# 导入城市省份资源文件
from citydict import CITY_MAP

# 导入爬虫程序
from spider import parse_goods_info
import time

def main():
    good = input('请输入需要查询的商品:\t')
    city = input('请输入查询城市:\t')
    provinceId = CITY_MAP[city]['provinceId']
    cityid = CITY_MAP[city]['cityid']
    # 构造商品搜索的链接
    searc_url = 'http://search.yhd.com/c0-0/k' + good
    print('正在搜索相关商品')
    # 调用我们写的爬虫抓取数据
    res = parse_goods_info(searc_url, provinceId, cityid)
    print('搜索完毕.....正在处理数据')
    # 格式化输出一下
    for rec in res:
        print('型号: {}\t价格: {}\t库存: {}\t地址: {}'.format(
            rec['name'], rec['price'], rec['stock'], rec['url']))
        time.sleep(0.5)

if __name__ == '__main__':
    main()

总结

本次爬虫的难度不大,
但是却是一个十分有用的程序

我来假设一个使用场景:

我炒鸡想要某个商品,可是这部商品坑爹要抢购
我怎么样才能第一时间知道这个商品补货/上架的时间呢?
用这个脚本 定时循环爬取,
一旦库存>0 我们就给用户发送通知~

当然,我也只是想想,毕竟有货了我也买不起 (逃~)

每天的学习记录都会 同步更新到:
微信公众号: findyourownway

知乎专栏:zhuanlan.zhihu.com/Ehco

blog : www.ehcoblog.ml

Github: github.com/Ehco1996/Pyt

编辑于 2017-09-01

文章被以下专栏收录