BD第2课:抓取天猫商城胸罩销售数据

本例会使用 Urllib 3 模块从天猫商城获取胸罩销售数据,抓取胸罩销售数据分为如下两步:

抓取指定商品的所有销售数据(评论数据) 获取商品列表 由于天猫商城对每个商品返回的评论数是有限制的,最多只能返回 99 页,每页 20 条,也就是说,每个商品最多可以获得近 2000 条销售数据,为了尽可能获取更多的销售数据,应该从多个商品中获取销售数据,所以就需要抓取商品列表。为了方便,本例只抓取搜索页面第一页的商品列表。

抓取指定商品销售数据需要使用 JSON 模块中相应的 API 进行分析,因为返回的销售数据是 JSON 格式的,而从搜索页面抓取的商品列表需要分析 HTML 代码,所以本例会使用 BeautifulSoup。在对数据进行分析整理后,需要将数据保存到 SQLite 数据库中,因此,本例还会使用 SQLite 3 模块,本例使用的其他模块还包括 OS 和 RE。所以需要在 Python 脚本开头使用下面的代码导入相关模块。

        from urllib3 import *
import sqlite3
import json
import re
import os
from bs4 import BeautifulSoup
      

为了让读者对本系列文章涉及到的知识有一个基本的认识,也考虑到没有接触过这方面知识的读者,本文会首先介绍一下这些技术的基本使用方法。

Urllib 3

Urllib 3 是一个功能强大、条理清晰、用于编写 HTTP 客户端的 Python 库,许多 Python 的原生系统已经开始使用 Urllib 3,其提供了很多 Python 标准库里所没有的重要特性,这些特性包括:

线程安全 连接池 客户端 SSL/TLS 验证 使用 Multipart 编码上传文件 协助处理重复请求和 HTTP 重定位 支持压缩编码 支持 HTTP 和 SOCKS 代理 Urllib 3 并不是 Python 语言的标准模块,因此,使用 Urllib 3 之前需要使用 pip 命令或 conda 命令安装 Urllib 3。

        pip install urllib3
      

下面就用 Urllib 3 的相应 API 发送 HTTP 请求来讲解一下 Urllib 3 的用法。使用 Urllib 3 中的 API 向服务端发送 HTTP 请求,首先需要引用 Urllib 3 模块,然后创建 PoolManager 类的实例,该类用于管理连接池;最后就可以通过 Request 方法发送 GET 请求了,Request 方法的返回值就是服务端的响应结果,通过 Data 属性直接可以获得服务端的响应数据。看看,是不是非常简单呢!

当向服务端发送 HTTP GET 请求时,而且请求字段值包含中文、空格等字符,需要对其进行编码,在 urllib.parse 模块中有一个 urlencode() 函数,可以将一个字典形式的请求值对作为参数传入 urlencode() 函数,该函数返回编码结果。

        # 使用urlencode函数将“极客起源“转换为URL编码形式
print(urlencode({'wd':'极客起源'}))
      

执行上面的代码,会输出如下的内容。

        wd=%E6%9E%81%E5%AE%A2%E8%B5%B7%E6%BA%90
      

使用 Request 方法发送 HTTP GET 请求时,可以使用 urlencode() 函数对 GET 字段进行编码,也可以直接使用 fields 关键字参数指定字典形式的 GET 请求字段。使用这种方式,Request 方法会自动对 fields 关键字参数指定的 GET 请求字段进行编码。

        # http是PoolManager类的实例变量
http.request('GET', url,fields={'wd':'极客起源'})
      

如果要向服务端发送比较复杂的数据,通过 HTTP GET 请求就不太合适,因为 HTTP GET 请求将要发送的数据都放到了 URL 中。因此,当向服务端发送复杂数据时建议使用 HTTP POST 请求。

HTTP POST 请求与 HTTP GET 请求的使用方法类似,只是在向服务端发送数据时,传递数据会跟在 HTTP 请求头后面,因此,可以使用 HTTP POST 请求发送任何类型的数据,包括二进制形式的文件(一般会将这样的文件使用 Base 64 或其他编码格式进行编码)。下面的代码演示了如何向服务的发送 HTTP POST 请求。

        from urllib3 import *
disable_warnings()
http = PoolManager()
# 指定要提交HTTP POST请求的URL,/register是路由
url = 'http://localhost:5000/register'
# 向服务端发送HTTP POST请求,用fields关键字参数指定HTTP POST请求字段名和值
response = http.request('POST', url,fields={'name':'李宁','age':18})
# 获取服务端返回的数据
data = response.data.decode('UTF-8')
# 输出服务端返回的数据
print(data)
      

SQLite 3

通过 SQLite 3 模块中提供函数可以操作 SQLite 数据库,SQLite 3 模块是 Python 语言内置的,不需要安装,直接导入该模块即可。

SQLite 3 模块中提供了丰富的函数可以对 SQLite 数据库进行各种操作,不过在对数据进行增、删、改、查以及其他操作之前,先要使用 connect() 函数打开 SQLite 数据库,通过该函数的参数指定 SQLite 数据库的文件名即可。打开数据库后,通过 cursor 方法获取 sqlite3.Cursor 对象,然后通过 sqlite3.Cursor 对象的 execute 方法执行各种 SQL 语句,如创建表、创建视图、删除记录、插入记录、查询记录等。如果执行的是查询 SQL 语句(SELECT 语句),那么 execute 方法会返回 sqlite3.Cursor 对象,需要对该对象进行迭代,才能获取查询结果的值。

        import sqlite3
import os

dbPath = 'data.sqlite'
# 只有data.sqlite文件不存在时才创建该文件
if not os.path.exists(dbPath):
    # 创建SQLite数据库
conn = sqlite3.connect(dbPath)
# 获取sqlite3.Cursor对象
c = conn.cursor()
# 创建persons表
    c.execute('''CREATE TABLE persons
       (id INT PRIMARY KEY     NOT NULL,
       name           TEXT    NOT NULL,
       age            INT     NOT NULL,
       address        CHAR(50),
       salary         REAL);''')

    # 修改数据库后必须调用commit方法提交才能生效
conn.commit()
# 关闭数据库连接
    conn.close()
    print('创建数据库成功')
      

Beautiful Soup

Beautiful Soup 是第三方的开发库,在使用之前需要安装。读者可以使用下面的命令安装 Beautiful Soup。

        pip install beautifulsoup4
      

安装 Beautiful Soup 后,在 Python 的 REPL 环境中执行下面的代码,如果未抛出异常,就说明 Beautiful Soup 已经安装成功了。

        import bs4
      

bs4 模块中有一个核心类 BeautifulSoup,该类构造方法的第1个参数可以指定要分析的 HTML 代码,第2个参数表示 HTML 分析引擎,建议使用 lxml,因为解析速度比较快,lxml 是第三方的 Python 模块,都需要单独安装。

        conda install lxml
      

下面的代码演示了如何使用 BeautifulSoup 分析 HTML 代码, 并获取 HTML 代码中的相应内容。

        from bs4 import BeautifulSoup
# 使用html.parser引擎
soup1 = BeautifulSoup('<title>html.parser测试</title>','lxml')
# 获取title标签
print(soup1.title)
# 获取title标签中的文本
print(soup1.title.text)
      

OK,现在不必要的知识都讲完了,下面开始正式分析天猫商城的商品评论数据。

从上一篇的分析结果可知,天猫商城的商品评论数据是通过list_detail_rate.htm页面获取的 JSON 数据,在这个 URL 中有一个 currentPage 参数可以设置当前获取的评论页数,如图1所示。


v2-6c2f95565f84b7f3f39e8a6898ea7933_b.jpg


图1 获取评论页数

不过将这个 URL 在浏览器中访问后,会显示如图2所示的内容,很明显,这并不是纯粹的 JSON 数据,前面还有 jsonp1974,以及其他一些不属于 JSON 数据的内容,我们不需要管这些是什么东西,直接去掉就可以了。这个 jsonp1974 就是 URL 最后的 callback 参数的值,应该是一个回调函数,我们不需要管他,直接去掉就 ok 了。


v2-012307177c3a2791bb8bb8699e95c37a_b.jpg


图2 JSON 数据

去掉这些零碎的方法很多,本例采用了直接替换的方式,代码如下:

        # itemId是商品ID,currentPage是页码,从1开始
def getRateDetail(itemId,currentPage):
    # 获取评论数据的Url
    url = 'https://rate.tmall.com/list_detail_rate.htm?itemId=' + str(itemId) + '&spuId=837695373&sellerId=3075989694&order=3&currentPage=' + str(currentPage) + '&... ... callback=jsonp1278'
    # 向服务的发送请求  
    r = http.request('GET',url,headers = headers)
    # 用GB18030格式转码
    c = r.data.decode('GB18030')
    # 替换相应的字符串
    c = c.replace('jsonp1278(','')
    c = c.replace(')','')
    c = c.replace('false','"false"')
    c = c.replace('true','"true"')
    # 将json数据转换为字典对象
    tmalljson = json.loads(c)
    return tmalljson
      

由于天猫商城服务端会检测浏览器类型,所以在向服务端发送 HTTP 请求是需要加上 HTTP 请求头,为了方便,这里将 HTTP 请求头的信息都放到了 headers.txt 文件中,并编写了 str2Headers 函数,将 headers.txt 文件中的 HTTP 请求头转换为字典对象。

        def str2Headers(file):
    headerDict = {}
    f = open(file,'r')
    headersText = f.read()
    headers = re.split('\n',headersText)
    # 将每一行http请求拆分成key和value
    for header in headers:
        result = re.split(':',header,maxsplit = 1)
        headerDict[result[0]] = result[1]
    f.close()
    return headerDict
# 将headers.txt文件中的http请求头转换为字典对象(headers)
headers = str2Headers('headers.txt')
      

接下来的任务就是抓取搜索页面的所有商品列表,搜索页面是search_product.htm,后面会跟一堆参数,不需要管他,只需要从返回的 HTML 代码中使用 BeautifulSoup 进行分析,得到我们想要的结果即可,实现代码如下:

        # 以列表形式返回商品ID
def getProductIdList():
    # 用于搜索商品的Url
    url = 'https://list.tmall.com/search_product.htm?spm=a220m.1000858.1000724.4.38b7981eyLhA9O&cat=50025983&q=%D0%D8%D5%D6&sort=d&style=g&from=mallfp..pc_1_searchbutton&smAreaId=210100#J_Filter'
    r = http.request('GET', url,headers = headers)
    c = r.data.decode('GB18030')
    soup = BeautifulSoup(c,'lxml')
    linkList = []
    idList = []
    # 搜索满足条件的标签
    tags = soup.find_all(href=re.compile('detail.tmall.com/item.htm'))
    for tag in tags:
        # 将符合条件的href添加到列表中
        linkList.append(tag['href'])
    # 去掉重复的Url(利用Set的特性)
    linkList = list(set(linkList))
    for link in linkList:
        aList = link.split('&')
        idList.append(aList[0].replace('//detail.tmall.com/item.htm?id=',''))
    return idList
      

最后就很容易了,只需要利用前面给出的两个函数抓取评论数据,然后利用 SQLite 3 模块中的 API 将抓取的数据写入 SQLite 数据库即可,下面看一下完整的实现代码。

        from urllib3 import *
import sqlite3
import json
import re
import os
from bs4 import BeautifulSoup
disable_warnings()
# 创建数据库
dbPath = 'bra.sqlite'
if os.path.exists(dbPath):
    os.remove(dbPath)
conn = sqlite3.connect(dbPath)
cursor = conn.cursor()
cursor.execute('''create table t_sales
            (id integer primary key autoincrement not null,
            color text not null,
            size text not null,
            source text not null,
            discuss mediumtext not null,
            time text not null);''')
conn.commit()

# Cookie劫持
http = PoolManager()
def str2Headers(file):
    headerDict = {}
    f = open(file,'r')
    headersText = f.read()
    headers = re.split('\n',headersText)
    for header in headers:
        result = re.split(':',header,maxsplit = 1)
        headerDict[result[0]] = result[1]
    f.close()
    return headerDict
headers = str2Headers('headers.txt')
def getRateDetail(itemId,currentPage):
    url = 'https://rate.tmall.com/list_detail_rate.htm?itemId=' + str(itemId) + '&spuId=837695373&sellerId=3075989694&order=3&currentPage=' + str(currentPage) + '&... &callback=jsonp1278'
    r = http.request('GET',url,headers = headers)
    c = r.data.decode('GB18030')
    c = c.replace('jsonp1278(','')
    c = c.replace(')','')
    c = c.replace('false','"false"')
    c = c.replace('true','"true"')
    tmalljson = json.loads(c)
    return tmalljson
tmalljson = getRateDetail('537808595989',10)
#print(getRateDetail('19628167605',1))

def getLastPage(itemId):
    tmalljson = getRateDetail(itemId,1)
    return tmalljson['rateDetail']['paginator']['lastPage']
def getProductIdList():
    url = 'https://list.tmall.com/search_product.htm?spm=a220m.1000858.1000724.4.38b7981eyLhA9O&cat=50025983&q=%D0%D8%D5%D6&sort=d&style=g&from=mallfp..pc_1_searchbutton&smAreaId=210100#J_Filter'
    r = http.request('GET', url,headers = headers)
    c = r.data.decode('GB18030')
    soup = BeautifulSoup(c,'lxml')
    linkList = []
    idList = []
    tags = soup.find_all(href=re.compile('detail.tmall.com/item.htm'))
    for tag in tags:
        linkList.append(tag['href'])
    linkList = list(set(linkList))
    for link in linkList:
        aList = link.split('&')
        idList.append(aList[0].replace('//detail.tmall.com/item.htm?id=',''))
    return idList

print(getLastPage('19628167605'))
initial = 0
productIdList = getProductIdList()
while initial < len(productIdList):
    try:
        itemId = productIdList[initial]
        print('----------',itemId,'------------')
        maxnum = getLastPage(itemId)
        num = 1
        while num <= maxnum:
            try:
                tmalljson = getRateDetail(itemId, num)
                rateList = tmalljson['rateDetail']['rateList']
                n = 0
                while n < len(rateList):
                    # 颜色分类:H007浅蓝色加粉色;尺码:32/70A
                    colorSize = rateList[n]['auctionSku']
                    m = re.split('[:;]',colorSize)
                    rateContent = rateList[n]['rateContent']
                    color = m[1]
                    size = m[3]
                    dtime = rateList[n]['rateDate']
                    cursor.execute('''insert into t_sales(color,size,source,discuss,time) 
                                    values('%s','%s','%s','%s','%s') ''' % (color,size,'天猫',rateContent,dtime))
                    conn.commit()
                    n += 1
                    print(color)
                print(num)
                num += 1
            except Exception as e:
                continue        
        initial += 1
    except Exception as e:
        print(e)

conn.close()
      

现在可以运行这个 Python 脚本文件,首先会自动建立名为 bra.sqlite 的 SQLite 数据库,然后会将抓取的数据保存到 bra.sqlite 数据库中的t_sales表中,效果如图3所示。


v2-d86363248ed1eb465e9bc64e04604b62_b.jpg


如果你有疑问欢迎加微信咨询:

v2-52b6f837be283214a9815bdaf2650b34_b.jpg

也可以关注我的公众号想我提问:

v2-0a9159f405d92730d1183d93d381d65d_b.jpg

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值