网络爬虫概要

网络爬虫是一种按照一定的规则,自动地抓取网站信息的程序或者脚本。

本文以 Python 为例简要谈谈爬虫是如何编写的。

如何爬取网站信息

写爬虫之前,我们必须确保能够爬取目标网站的信息。

因此,先要弄清以下三个问题:

  1. 网站是否已经提供了 api

  2. 网站是静态的还是动态的

  3. 网站是否有反爬的对策

情形 1:开放 api 的网站

一个网站倘若开放了 api,那你就可以直接 GET 到它的 json 数据。

比如xkcd 的 about 页就提供了 api 供你下载

import requests
requests.get('https://xkcd.com/614/info.0.json').json()

那么如何判断一个网站是否开放 api 呢?有 3 种方法:

  1. 在站内寻找 api 入口

  2. 用搜索引擎搜索“某网站 api”

  3. 抓包。有的网站虽然用到了 ajax(比如果壳网的瀑布流文章,亦或是unsplash的瀑布流图片),但是通过抓包还是能够获取 XHR 里的 json 数据的,不要傻乎乎地去用 selenium,反而会降低效率。

怎么抓包:F12 - Network - F5 刷新

实际上,app 的数据也可以通过抓包来获取。

app 抓包

安装fiddler并启动,打开 Tools-Options-Connections,将 Allow remote computers to connect 打上勾并重启 fiddler。

命令行上输入 ipconfig,查看自己网络的 ipv4 地址,在手机的网络上设置 HTTP 代理,端口为 8888。

这时虽说能抓到数据,但都是 HTTP 的,而 app 的大部分数据都是 HTTPS 的。

在 Options-HTTPS 中将 Decrypt HTTPS traffic 打上勾。

以 ios 系统为例,在 Safari 浏览器中输入http://ipv4:8888,下载证书并安装。

这样就能抓到 HTTPS 数据了。

情形 2:不开放 api 的网站

如果此网站是静态页面,那么你就可以解析它的 HTML。

解析库强烈推荐parsel,不仅语法和 css 选择器类似,而且速度也挺快,Scrapy 用的就是它。

你需要了解一下css 选择器的语法xpath也行),并且学会看网页的审查元素(F12 键)

比如获取 konachan 的所有原图链接

from parsel import Selector
res = requests.get('https://konachan.com/post')
tree = Selector(text=res.text)
imgs = tree.css('a.directlink::attr(href)').extract()

如果此网站是动态页面,先用 selenium 来渲染 JS,再用 HTML 解析库来解析 driver 的 page_source。

比如获取 hitomi.la 的数据(这里把 chrome 设置成了无头模式)

from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_argument('--headless')
driver = webdriver.Chrome(options=options)
driver.get('https://hitomi.la/type/gamecg-all-1.html')
tree = Selector(text=driver.page_source)
gallery_content = tree.css('.gallery-content > div')

情形 3:反爬的网站

目前的反爬策略常见的有:User-Agent 检测、Referer 防盗链、验证码、登录、封 ip 等。

前两个最简单:在请求的 headers 中把它们替换掉就行了

验证码:利用打码平台破解(如果硬上的话用 opencv 或 keras 训练图)

登录:利用 requests 的 post 或者 selenium 模拟用户进行模拟登陆

封 ip:买些代理 ip(免费 ip 一般都不管用),requests 中传入 proxies 参数即可

如何编写结构化的爬虫

爬虫的结构很简单,无非就是创造出一个 tasklist,对 tasklist 里的每一个 task 调用 crawl 函数。

大多数网页的 url 构造都是有规律的,你只需根据它用列表推导式来构造出 tasklist

如果追求速度的话,可以考虑用 concurrent.futures 或者 asyncio 等库。

from pathlib import Path
from concurrent import futures
import requests
from parsel import Selector

domain = 'https://www.doutula.com'
total = []


def crawl(url):
    res = requests.get(url)
    tree = Selector(text=res.text)
    imgs = tree.css('img.lazy::attr(data-original)').extract()
    total.extend(imgs)


if __name__ == '__main__':
    tasklist = [f'{domain}/article/list/?page={i}' for i in range(1, 551)]
    with futures.ThreadPoolExecutor(50) as executor:
        executor.map(crawl, tasklist)
    Path('doutula.txt').write_text('\n'.join(total))

数据存储的话,看需求,存到数据库中的话只需熟悉对应的驱动即可。

常用的数据库驱动有:pymysql(MySQL), pymongo(MongoDB)

还有一种方法就是直接利用现成的框架来编写:looter是笔者写的一个轻量级框架,适合中小型项目;比较大型的项目建议用scrapy

Last updated