0x01 写在前面

本文不会讲到如何使用 Scrapy 框架,也不会讲述 requests 自己构建爬虫。

而是重在一种过程:如何快速利用所有手段构建一个自己想爬取信息的小爬虫。

本文只在乎了流程,其中还有很多小细节需要细细完善。一个好的爬虫,需要花费大量的精力。


0x02 工具介绍&使用介绍

2.1 Scrapy

Scrapy(/ˈskreɪpi/ SKRAY-pee是一个用Python编写的自由且开源的网络爬虫框架。它在设计上的初衷是用于爬取网络数据,但也可用作使用API来提取数据,或作为生成目的的网络爬虫。该框架目前由网络抓取的开发与服务公司Scrapinghub公司维护。

Scrapy项目围绕“蜘蛛”(spiders)建构,蜘蛛是提供一套指令的自包含的爬网程序(crawlers)。遵循其他如Django框架的一次且仅一次精神,允许开发者重用代码将便于构建和拓展大型的爬网项目。Scrapy也提供一个爬网shell,开发者可用它测试对网站的效果。

——摘自维基百科。

简单说就是一个专门为开发爬虫设计的,比较适合大爬虫,这时候的性能优化、调度啥的都很方便,但是小爬虫就反而显得麻烦了。因此本文不会重点使用他,仅仅是简单利用一下他。

2.2 requests

Requests is a Python HTTP library, released under the Apache License 2.0. The goal of the project is to make HTTP requests simpler and more human-friendly. The current version is 2.25.0

Requests is one of the most popular python libraries that is not included with python, it has been proposed that requests be distributed with python by default

——摘自维基百科。

2.3 知识储备

看懂本文至少需要会哪些知识

2.3.1 Html+CSS+js

由于爬虫就是和网页打交道,因此前端的各种语言不说精通,但是基本的使用还是应知道的。

2.3.2 python

这个就有点废话了,本文就是基于 python 来进行爬取的。

2.3.3 requests

不需要知道全部,这里只需要知道这个是 python 的一个库,可以用来进行 http 的请求的即可。需要的时候再到官方文档查询需要的函数。

2.3.4 浏览器开发者工具的基本使用

为了选中各种元素,需要通过浏览器的开发者模式对前端进行调控和选择,因此基本的开发者调试功能的必须的。

2.3.5 Scrapy

不需要知道那么多,就使用下面的这种也就够了。

Scrapy shell 使用

打开一个 终端,运行命令。

格式如下:

1
scrapy shell url

例如,输入下面的命令并运行,

1
scrapy shell https://app.mi.com/details?id=com.planet.light2345

运行 Scrapy shell

使用 Scrapyshell 会返回一个 ipython 的会话,其中的封装好了一个可以直接使用的对象 response

终端 ipython

内置的 response 对象,封装了很多方法,这里主要使用其中的 xpath 方法。更多请查阅官方文档

内置 response 对象


0x03 环境安装

Scrapy 安装

1
pip install scrapy

requests 安装

1
pip install requests


0x04 实战演示

4.1 目标

4.1.1 目标站点

笔者通过百度搜索,找到了目标网站—— “小米应用商店”。

小米应用市场

这里感谢小米,感谢雷军。

4.1.2 爬取目的

本次爬取是希望将 小米应用市场 中的所有的有效的 热门应用 的隐私政策爬取出来,并进行简单的数据清洗。

4.2 step-by-step

4.2.1 准备工作

1、浏览器访问此网站,并打开开发者模式。(F12)。

2、打开一个此网站的 Scrapy shell

4.2.2 初步分析

首先是如何获取每个页面。将页面滚动到底部,可以发现有多个页面。

页面按钮

随便点击一个页面,观察浏览器顶部 url 中的变化,可以发现页面获取的 get 参数信息。

1
2
3
4
# 点击页面 2 时
https://app.mi.com/allHotList?page=2
# 点击页面 5 时
https://app.mi.com/allHotList?page=5

所以任意页面的参数就是 page 参数。所以,我们只需要将最后的参数进行枚举即可以遍历每个页面。

至于页面的总数量,偷懒可以直接硬编码 23,因为我们都肉眼可见就是 23 页。想让程序蒲适性好些就可以通过页面标签来获取。

于是就可以通过 Scrapy 提供的 shell 工具获取我们所希望的信息了。

4.2.3 定位总页码

step1:

首先通过浏览器的开发者工具选中我们要定位的目标。

选中a标签

step2:

然后右键该元素,并选择 复制 -> 复制 xpath 。从而得到目标元素的 xpath 路径,熟悉的读者也可以自己手动定位。

复制xpath

step3:

然后切换到前面打开的 Scrapyshell 终端中,输入下面的代码,并执行。

1
response.xpath("浏览器中复制的xpath")

如下图所示,返回值是一个列表,其中的第一个元素就是我们选择的元素。

选中页面总数元素

step4:

然后通过 text() 获取此标签中的文本值。输入以下代码,获取值。

1
response.xpath("/html/body/div[6]/div/div[1]/div[2]/a[7]/text()")

可以看到返回的值的确包含了想要的数据 23,但是仍然是一个对象的列表。

获取页面总数

所以可以通过内置的 get 函数获取其中的 data 的内容(字符串格式)

1
response.xpath("/html/body/div[6]/div/div[1]/div[2]/a[7]/text()")[0].get()

得到页面数

于是得到了页面的数量,记得通过 int 进行类型转化。

为了发包方便,笔者这里封装了一个函数 send 来实现。

1
2
3
4
5
6
def send(url):
"""
获取一个 url get 信息
"""

return requests.get(url).text

所以可以得到下面的代码。

1
2
3
4
5
6
def getPageNum(url):
"""
获取页面总数
"""

return int(Selector(text=send(url)).xpath('/html/body/div[6]/div/div[1]/div[2]/a[7]/text()')[0].get())

4.2.4 获取页面中 app 的 url

有了上述的页面,就可以用同样的方法获取每个页面中每个 appurl

step1:

使用开发者工具选中所有 app 的列表。

如下图所示,此 ul 包含了本页面中的所有的应用。然后就可以选择其中的所有元素。

选中ul标签

step2:

同样右键复制其 xpath 路径。再在前面打开的 Scrapyshell 终端中,输入下面的代码,并执行。

1
response.xpath("/html/body/div[6]/div/div[1]/div[1]/ul")

可以看见获取是成功的。

获取ul

step3:

而通过观察,其中的每个 app 都对应者一个 li 。因此前面的路径下面,可以添加一个 li 即可以选中所有的应用的 li

1
response.xpath("/html/body/div[6]/div/div[1]/div[1]/ul/li")

可以看到,返回的值是一个 python 中的列表,列表的每个元素都是其中的一个 li 对应的选择器。

获取应用所在li标签

但是事实上,这里只需要其中的每个 li 下面的 a 标签中的链接。于是应该再添加一个 a

1
response.xpath("/html/body/div[6]/div/div[1]/div[1]/ul/li/a")

和上面类似,只是内容更进一步了,但是还不是我们想要的。

获取应用所在a标签

step4:

在上述的基础上,通过观察发现需要获取 a 标签中的 href的值。

于是再使用下面的方法获取其中的值。使用 @href 可以获取所有的 href

1
response.xpath("/html/body/div[6]/div/div[1]/div[1]/ul/li/a/@href")

获取a标签的href

再通过函数 getall() 可以获得所有的这些链接组成的列表。

1
response.xpath("/html/body/div[6]/div/div[1]/div[1]/ul/li/a/@href").getall()

获取应用链接列表

于是我们就得到了我们想要的 url 的列表。

1
2
3
4
5
6
7
8
def getIdInPagei(pagei):
"""
获取每一页中的所有软件的链接
"""

pageUrl = 'https://app.mi.com/allHotList?page=' + str(pagei)

return Selector(text=send(pageUrl)).xpath("/html/body/div[6]/div/div[1]/div[1]/ul/li/a/@href").getall()

4.2.5 获取每个软件隐私政策 url

有了上述的工作之后,便可以通过进入某个 app 对应的页面,从而获取其名字和隐私政策的 url

通过观察,可以发现每个应用进入之后都非常一致,在统一的位置都放上的 隐私政策 的链接,这很利于批量爬取。

查看app-1

查看app-2

但是部分应用开发者尚未完善隐私政策的链接,因此后面有些爬取出来会是 “javascript:void(0)” 等各自奇怪的情况。

step1:

重新打开一个新的 Scrapyshell 。这次的 url 应该以某一个具体的 app 为例子。这里我们以某一个作为例子。

1
scrapy shell https://app.mi.com/details?id=com.planet.light2345

step2:

还是同理,通过前面的方法,获取对应的 xpath 路径。

选取元素

然后这里就直接一步到位,不再啰嗦,就直接自己添加一个 /@href

1
response.xpath("/html/body/div[6]/div[1]/div[5]/div[2]/div[2]/a/@href").get()

获取隐私链接

于是这个就是对应的隐私政策的链接了。

step3:

然后需要获取此应用的名字,还是同理选中这个元素。

选中元素

然后复制其对应的 xpath 并取其中的值即可(添加 /text())。

1
response.xpath("/html/body/div[6]/div[1]/div[2]/div/div/h3/text()").get()

可以看到,成功正确获取到了此应用的名字。

获取名字

step4:

结合前面获取的每个 app 的页面,都可以获取其相应的隐私政策的链接和其名字。

1
2
3
4
5
6
7
8
9
10
11
12
def getPriUrlAndName(appId):
"""
从某一页中点进某个软件后,再获取其隐私政策的链接 和 软件的名字
"""

appUrl = 'https://app.mi.com' + str(appId)
res = Selector(text=send(appUrl))

url = res.xpath("/html/body/div[6]/div[1]/div[5]/div[2]/div[2]/a/@href").get()
name = res.xpath("/html/body/div[6]/div[1]/div[2]/div/div/h3/text()").get()

return url, name

4.2.6 从页面获取隐私政策内容

有了上述的铺垫,即可以将隐私政策从页面中获取下来了。

本次由于是获取文本信息,只需要将整个页面中所有的文本信息全部保存即可完成本次的数据的清洗。

这个需求只需要如下的方法,在 xpath 中已经集成。如下即是获取 body 这个变量名所代表的的 Selector 中的所有文本信息,包括子标签。

1
body.xpath('string(.)')

综上,于是可以写成下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def getPriFromUrl(url, name):
"""
从这个链接获取其中的隐私政策文本信息
"""

try:
res = Selector(text=send(url))
body = res.xpath("//body")[0]

# remove 没有返回值 是直接操作其中的对象
# 移除 js
body.xpath("//script").remove()

with open(f"./pri/{name}.txt", 'w', encoding='utf-8') as f:
f.write(str(body.xpath('string(.)').get()))
except:
print(f"--------->bad app {name} url: {url}")

值得一提的是,直接使用会将页面中的 JS 代码也一并获得,因此需要先将其移除,使用的是如下方法: body.xpath("//script").remove()

而使用 try ... except... 则是前面所说的,有些应用开发不完善,其隐私政策的 url 链接是无效的。

4.2.7 跑一个

直接运行。可以看到如下的提示。

运行脚本

然后可以看到文件夹下面生成的爬取文件了。效果还是。

爬取结果


0x05 分析总结

正如开头所说的,本文只是做了一个简单的实验,希望抛砖引玉。其中还有很多细节没有进行完善:如假 header 用于伪装、数据的清洗、尚存的乱码和需要 JS 渲染的页面需要使用 selenium (有空再将整理后的文档上传)等。本次只是梳理记录一个流程和方法。

更多的 xpath 用法可以参考 parsel (也支持 CSS 选择器)或者 xpath 的官方文档。

更多的 Scrapy 使用方法也请查阅官方文档。


0x06 参考

xpath的一些用法: https://www.cnblogs.com/g2thend/p/12452186.html

parsel文档:https://parsel.readthedocs.io/en/latest/usage.html

提取所有文本: https://blog.csdn.net/MrLevo520/article/details/53158050

requests: https://requests.readthedocs.io/en/master/

Scrapy:https://docs.scrapy.org/en/latest/