# 1. 爬虫概述
# 1.1 爬虫简介
爬虫,也称为网页蜘蛛、网络机器人或网络抓取程序,是一种用于从互联网上自动提取信息的计算机程序。爬虫的工作原理是从一个已知的初始网页开始,然后通过跟踪并下载这个页面上的所有链接来获取新的页面,如此反复,直到满足一定的条件或达到预定的深度。
下面是爬虫的基本工作流程:
- 选择起始URL:选择一个或多个起始网址作为爬虫的入口点。
- 发起请求:向这些URL发送HTTP请求,获取网页内容。
- 内容解析:解析获取的网页内容,提取所需的数据。
- URL提取:从当前页面中提取其他页面的链接。
- 数据存储:将提取的数据存储到数据库或其他存储介质中。
- 循环:使用之前提取的URL重复上述步骤,直到满足终止条件。
爬虫在实际运行时可能会遇到各种问题和挑战,如:
- 反爬策略:许多网站会采用各种手段来阻止或限制爬虫的访问,如:验证码、IP限制等。
- 动态内容:现代的网页很多都使用JavaScript动态加载内容,这使得传统的爬虫难以提取。
- 编码问题:不同的网站可能使用不同的字符编码,需要处理编码转换问题。
- 并发问题:为了提高爬取速度,可能需要使用多线程或多进程。但这也可能导致IP被封或网站服务器过载。
# 1.2 常规方式采集
# 1.2.1 Scrapy框架
Scrapy 是用 Python 实现的一个为了爬取网站数据、提取结构性数据而编写的应用框架,常应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。
项目地址:https://github.com/scrapy/scrapy (opens new window)

模块说明:
- Scrapy Engine(引擎):负责Spider、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等。
- Scheduler(调度器):它负责接受引擎发送过来的Request请求,并按照一定的方式进行整理排列,入队,当引擎需要时,交还给引擎。
- Downloader(下载器):负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine,由引擎交给Spider来处理,
- Spider(爬虫):它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler。
- Item Pipeline(管道):它负责处理Spider中获取到的Item,并进行进行后期处理(详细分析、过滤、存储等)的地方。
- Downloader Middlewares(下载中间件):你可以当作是一个可以自定义扩展下载功能的组件。
- Spider Middlewares(Spider中间件):可以理解为是一个可以自定扩展和操作引擎和Spider中间通信的功能组件。
# 1.2.2 feapder框架
feapder是一款上手简单,功能强大的Python爬虫框架,内置AirSpider、Spider、TaskSpider、BatchSpider四种爬虫解决不同场景的需求。支持断点续爬、监控报警、浏览器渲染、海量数据去重等功能。更有功能强大的爬虫管理系统feaplat为其提供方便的部署及调度。
- 项目地址:https://github.com/Boris-code/feapder
- 官方文档:https://feapder.com
模块说明:
- spider 框架调度核心
- parser_control 模版控制器,负责调度parser
- collector 任务收集器,负责从任务队里中批量取任务到内存,以减少爬虫对任务队列数据库的访问频率及并发量
- parser 数据解析器
- start_request 初始任务下发函数
- item_buffer 数据缓冲队列,批量将数据存储到数据库中
- request_buffer 请求任务缓冲队列,批量将请求任务存储到任务队列中
- request 数据下载器,封装了requests,用于从互联网上下载数据
- response 请求响应,封装了response, 支持xpath、css、re等解析方式,自动处理中文乱码
流程说明:
- spider调度start_request生产任务
- start_request下发任务到request_buffer中
- spider调度request_buffer批量将任务存储到任务队列数据库中
- spider调度collector从任务队列中批量获取任务到内存队列
- spider调度parser_control从collector的内存队列中获取任务
- parser_control调度request请求数据
- request请求与下载数据
- request将下载后的数据给response,进一步封装
- 将封装好的response返回给parser_control(图示为多个parser_control,表示多线程)
- parser_control调度对应的parser,解析返回的response(图示多组parser表示不同的网站解析器)
- parser_control将parser解析到的数据item及新产生的request分发到item_buffer与request_buffer
- spider调度item_buffer与request_buffer将数据批量入库
# 1.3 模拟浏览器采集
# 1.3.1 Selenium框架
Selenium是一款用于测试Web应用程序的经典工具,它直接运行在浏览器中,仿佛真正的用户在操作浏览器一样,主要用于网站自动化测试、网站模拟登陆、自动操作键盘和鼠标、测试浏览器兼容性、测试网站功能等,同时也可以用来制作简易的网络爬虫。
项目地址:https://github.com/SeleniumHQ/selenium (opens new window)
# 1.3.2 Playwright框架
Playwright最开始是微软推出的一套Node.js开源API,用来自动化控制浏览器,对标就是谷歌的Puppeteer,之后Playwright引入了测试模块@playwright/test
逐渐聚焦到了Web自动化测试,现已经成为当今最流行的Web自动化测试框架。截止2023年8月GitHub已经有了53K的star。
项目地址:https://github.com/microsoft/playwright (opens new window)
优势特点:
- 对比老牌测试框架,比如Selenium,如果单Web自动化测试来说,各种对比文章几乎一边倒的结论都是Playwright要快很多。
- 对比新型测试框架,比如Cypress,同样是Playwright更快。
Playwright为什么快:
- 首先Playwright使用websocket直接连接浏览器(DevTools Protocol),无需像Selenium一样先连接WebDriver。
- Playwright的并发机制。Playwright Test可以定义workers的数量,也就是如果你的CPU核数量够多,那么最大化的并行数量也就更多,理论上就能成倍的加快测试速度。
- Playwright也支持sharding模式。也就是可以把测试分配到不同的机器上同时执行。
- Playwright有强大的查询和定位器,支持自动等待且重试。在Puppeteer阶段采用的是类Jquery的查询定位元素,亦或xpath,而Playwright一开始继承了这一点,但现在推出了全新的
Locator
类并淘汰了类Jquery的查询接口。
依赖安装:
$ pip3 install playwright
$ playwright install
2
使用示例:
# -*- coding: utf-8 -*-
from playwright.sync_api import sync_playwright
def run(playwright):
browser = playwright.chromium.launch()
context = browser.new_context()
page = context.new_page()
page.goto(your_url)
html_content = page.content()
browser.close()
return html_content
with sync_playwright() as playwright:
html_content = run(playwright)
# 这里是你的 HTML 解析代码
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1.4 无代码采集器采集
# 1.4.1 Web Scraper浏览器插件采集
除了通过Python爬虫采集方式之外,我们还可以使用浏览器插件进行采集,它的优点是简单易用、不用破解反爬,缺点是它是半自动化的、需要开着浏览器。
常用的免费采集插件有 Web Scraper (opens new window),它是一款网站数据提取工具,类似于爬虫,但不需要编写代码,使用门槛较低,适用于轻度的数据爬取,它主要以谷歌扩展插件的形式存在。这个插件如何使用,建议找个视频教程跟着操作一遍就会了。
# 1.4.2 EasySpider可视化采集
项目介绍:一个可视化爬虫软件,可以使用图形化界面,无代码可视化的设计和执行爬虫任务。只需要在网页上选择自己想要爬的内容并根据提示框操作即可完成爬虫设计和执行。同时软件还可以单独以命令行的方式进行执行,从而可以很方便的嵌入到其他系统中。
项目地址:https://github.com/NaiboWang/EasySpider (opens new window)(原理及介绍见作者的 硕士毕业论文 (opens new window))
使用教程:可视化爬虫EasySpider: MacOS系统设计和执行eBay网站爬虫任务教程 (opens new window)、EasySpider:一个很强大的可视化网络爬虫工具,100%开源完全免费 (opens new window)
# 1.4.3 八爪鱼采集器
八爪鱼采集器是一款全网通用的互联网数据采集器(商业版软件,免费功能就够用了,付费版提供云采集解决方案),模拟人浏览网页的行为,通过简单的页面点选(提供智能识别功能),生成自动化的采集流程,从而将网页数据转化为结构化数据,存储于文件或数据库等多种形式。
- 官方网站:https://www.bazhuayu.com (opens new window)
- 客户端下载:Mac版下载 (opens new window)、Win版下载 (opens new window)
# 2. 正则表达式
# 2.1 正则表达式简介
# 2.1.1 基本介绍
正则表达式是用来匹配字符的一种工具,它常被用在网页爬虫,文稿整理,数据筛选等方面。在Python中需要通过正则表达式对字符串进⾏匹配的时候,可以使⽤⼀个python自带的模块,名字为re。在Python中,前缀字符r用于创建"原始字符串",主要作用是在字符串中包含反斜杠字符(\)时,不会将其解释为转义字符。
# 2.1.2 匹配模式
pattern | 描述 |
---|---|
(pattern) | 它用于匹配模式 pattern 并将匹配的文本捕获到一个分组中。 |
(?:pattern) | 这是一个非捕获组。它用于匹配模式 pattern,但不会将匹配的文本捕获到分组中。非捕获组在不需要捕获匹配文本时很有用,可以提高正则表达式的性能,并减少内存消耗。 |
(?=pattern) | 正向预查。它用于匹配在当前位置之后出现的文本,但不包括当前位置。如果当前位置之后的文本与 pattern 匹配,正向预查将成功。这在需要匹配某些文本出现在另一些文本之后的情况下非常有用。 |
(?!pattern) | 负向预查。它用于匹配在当前位置之后不出现的文本。如果当前位置之后的文本与 pattern 不匹配,负向预查将成功。这在需要排除某些文本出现在另一些文本之后的情况下非常有用。 |
# 2.2 正则表达式字符匹配
# 2.2.1 匹配单个字符
字符 | 描述 |
---|---|
\d | 匹配一个数字字符。等价于[0-9] 。 |
\D | 匹配一个非数字字符。等价于[^0-9] 。 |
\w | 匹配包括下划线的任何单词字符。等价于[A-Za-z0-9] |
\W | 匹配任何非单词字符。等价于[^A-Za-z0-9] |
\n \r \f \t | 分别代表换行符、回车符、换页符和制表符 |
\s | 匹配任何空白字符,包括空格、制表符、换页符等。等价于[\f\n\r\t\v] |
\S | 匹配任何非空白字符,等价于[^\f\n\r\t\v] |
\A | 代表字符串的开始 |
\z | 代表字符串的结束 |
# 2.2.2 匹配多个字符
字符 | 描述 |
---|---|
\ | 转义字符。例如:n匹配n,\n 匹配一个换行符,\\ 匹配\,\( 匹配( |
^ | 匹配字符串开始的位置 |
$ | 匹配字符串结束的位置 |
. | 代表所有的单个字符,除了\n 、\r |
[…] | 代表在[]范围内的字符,比如[a-z]代表a到z的字符 |
[^…] | 代表不在[]范围内的字符 |
{n} | 匹配在{n}前面的字符,匹配确定的n次。比如:o{2}不能匹配Bob中的o,但是能匹配food中的两个o |
{n,m} | 最少匹配n次且最多匹配m次,例如:o{1,3}将匹配fooood中前三个o |
{n, } | 至少匹配n次,例如:o{2,}不能匹配Bob中的o,但能匹配fooood中的所有o |
* | 和{0, }同义,匹配* 前面的字符0次或多次,例如:zo* 能匹配z、zo以及zoo |
+ | 和{1,}同义,匹配+前面的字符1次或多次。比如zo+能匹配zo以及zoo |
? | 和{0,1}同义,匹配?前面的字符0次或1次。非贪婪模式,尽可能少的匹配所搜索的字符串 |
a|b | 匹配a或b |
() | 匹配括号里的内容 |
# 2.3 re模块函数
[1] re.match函数
语法:re.match(pattern, string, flags=0).尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()就返回none。匹配成功re.match方法返回一个匹配的对象。
参数:pattern匹配的正则表达式,string要匹配的字符串,flags标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等。
import re
result = re.match("itcast", "itcast.cn")
print(result.group())
>>> 输出内容
itcast
2
3
4
5
6
[2] re.compile 函数
语法:compile 函数用于编译正则表达式,生成一个正则表达式( Pattern )对象,供 match() 和 search() 这两个函数使用。
import re
pattern = re.compile(r'\d+')
m = pattern.match('one12twothree34four', 3, 10)
print(m.group())
>>> 输出内容
12
2
3
4
5
6
7
[3] re.search函数
语法:re.search 扫描整个字符串并返回第一个成功的匹配,如果没有匹配,就返回一个 None。re.match与re.search的区别:re.match只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;而re.search匹配整个字符串,直到找到一个匹配。
import re
ret = re.search(r"\d+", "阅读次数为9999")
print(ret.group())
>>> 输出内容
9999
2
3
4
5
6
[4] re.findall函数
语法:在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果没有找到匹配的,则返回空列表。注意: match 和 search 是匹配一次 findall 匹配所有。
import re
ret = re.findall(r"\d+", "python = 9999, c = 7890, c++ = 12345")
print(ret)
>>> 输出内容
['9999', '7890', '12345']
2
3
4
5
6
[5] re.finditer函数
语法:和 findall 类似,在字符串中找到正则表达式所匹配的所有子串,并把它们作为一个迭代器返回
import re
it = re.finditer(r"\d+", "12a32bc43jf3")
for match in it:
print(match.group())
>>> 输出内容
12
32
43
3
2
3
4
5
6
7
8
9
10
[6] re.sub函数
语法:sub表示替换,将匹配到的数据进⾏替换。re.sub(pattern, repl, string, count=0, flags=0)
import re
ret = re.sub(r"\d+", '998', "python = 997")
print(ret)
>>> 输出内容
python = 998
2
3
4
5
6
[7] re.split函数
语法:根据匹配进⾏切割字符串,并返回⼀个列表。re.split(pattern, string, maxsplit=0, flags=0)
含义:pattern匹配的正则表达式,string要匹配的字符串,maxsplit分隔次数,maxsplit=1 分隔一次,默认为 0,不限制次数
import re
res = re.split(r'[,;\.\\]', 'a,b;c.d\e')
print(res)
>>> 输出内容
['a', 'b', 'c', 'd', 'e']
2
3
4
5
6
# 3. 应对反爬的手段
# 3.1 使用ProxyPool爬虫代理IP池
使用Python爬虫抓取数据的时候,我们经常会触发目标网站的反爬虫措施(通常是封IP,需要输入验证才能继续访问),导致抓取中断。我们可以利用网上的代理IP网站(有免费的也有付费的)去构建代理池,封了就自动换一个IP继续抓取。
# 3.1.1 ProxyPool简介
ProxyPool (opens new window) 是一个爬虫代理IP池的项目,可以自动从代理IP网站抓取可用的代理,目前支持十余个免费代理源,也可以自己添加代理源。
官方提供的测试地址:http://demo.spiderpy.cn (opens new window) (懒得自己搭建的话也可以用这个,下面就不用看了)
# 3.1.2 搭建ProxyPool
[1] 搭建Redis缓存中间件
- ProxyPool的部署需要用到Redis缓存中间件,用以存储代理IP信息。
- 需要注意的是Redis数据库的版本不要太高,我最初用最新版报错,降级到3.2.8版本就好了。
[2] 使用Docker部署ProxyPool项目
依次执行以下命令,拉取镜像并创建容器。
$ docker pull jhao104/proxy_pool
$ docker run --name proxy_pool --env DB_CONN=redis://:"password"@ip:port/db -p 5010:5010 jhao104/proxy_pool:latest
$ docker update proxy_pool --restart=always
2
3
注:第二条命令db处填写的应该是0-15的一个数字,否则会报:redis.exceptions.ResponseError: invalid DB index
错误。
可以使用以下命令进入容器内部,配置文件是setting.py,我们可以根据自己的需求进行更改配置。
$ docker exec -it proxy_pool /bin/sh
$ vi setting.py
2
注:建议将检测代理IP可用性的网址换成你要爬取的目标网址。扩展代理也在这个文件里配置,详见项目的README吧,我这里就不赘述了。
[3] 配置HTTPS及反向代理
使用Nginx配置反向代理并开启HTTPS(反向代理5010端口)
# 3.1.3 ProxyPool的API服务
用Chrome浏览器打开:https://域名
,即可访问API接口服务。
# 3.1.4 在爬虫中使用ProxyPool
如果要在爬虫代码中使用的话, 可以将此API封装成函数直接使用,以下是官方提供的示例,仅供参考。
import requests
def get_proxy():
return requests.get("http://127.0.0.1:5010/get/").json()
def delete_proxy(proxy):
requests.get("http://127.0.0.1:5010/delete/?proxy={}".format(proxy))
# your spider code
def getHtml():
# ....
retry_count = 5
proxy = get_proxy().get("proxy")
while retry_count > 0:
try:
html = requests.get('http://www.example.com', proxies={"http": "http://{}".format(proxy)})
# 使用代理访问
return html
except Exception:
retry_count -= 1
# 删除代理池中代理
delete_proxy(proxy)
return None
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
注:由于代理池的节点并不全部有效,我们使用的时候仍然会有出错的情况。
# 3.2 使用ddddocr破解验证码
# 3.2.1 ddddocr简介
项目介绍:OCR通用验证码识别SDK免费开源版。它通过大批量生成随机数据后进行深度网络训练,本身并非针对任何一家验证码厂商而制作,本库使用效果完全靠玄学,可能可以识别,可能不能识别。
项目地址:https://github.com/sml2h3/ddddocr (opens new window)
# 3.2.2 ddddocr搭建服务
项目介绍:使用ddddocr的最简API搭建项目,支持Docker部署。
项目地址:https://github.com/sml2h3/ocr_api_server (opens new window)
$ git clone https://github.com/sml2h3/ocr_api_server.git
$ cd ocr_api_server
$ docker run -d --name ocr_server -p 9898:9898 ocr_server:v1
2
3
接口服务:接口的调用方式如下
http://{host}:{port}/{opt}/{img_type}/{ret_type}
- opt:操作类型 ocr=OCR det=目标检测 slide=滑块(match和compare两种算法,默认为compare)
- img_type: 数据类型 file=文件上传方式 b64=base64(imgbyte)方式 默认为file方式
- ret_type: 返回类型 json=返回json(识别出错会在msg里返回错误信息) text=返回文本格式(识别出错时回直接返回空文本)
2
3
4
# 3.2.3 ddddocr使用示例
该项目自带了各种验证码的测试文件,可以使用 test_api.py 进行测试,运行结果如下:
api_url='http://127.0.0.1:9898/ocr/file', resp.text=''
api_url='http://127.0.0.1:9898/ocr/file/json', resp.text='{"status": 200, "result": "", "msg": "module \'PIL.Image\' has no attribute \'ANTIALIAS\'"}'
api_url='http://127.0.0.1:9898/ocr/b64', resp.text=''
api_url='http://127.0.0.1:9898/ocr/b64/json', resp.text='{"status": 200, "result": "", "msg": "module \'PIL.Image\' has no attribute \'ANTIALIAS\'"}'
api_url='http://127.0.0.1:9898/det/file', resp.text='[[50, 7, 67, 24]]'
api_url='http://127.0.0.1:9898/det/file/json', resp.text='{"status": 200, "result": [[50, 7, 67, 24]], "msg": ""}'
api_url='http://127.0.0.1:9898/slide/match/file', resp.text="{'target_y': 45, 'target': [215, 45, 260, 91]}"
api_url='http://127.0.0.1:9898/slide/match/file/json', resp.text='{"status": 200, "result": {"target_y": 45, "target": [215, 45, 260, 91]}, "msg": ""}'
api_url='http://127.0.0.1:9898/slide/match/b64', resp.text="{'target_y': 45, 'target': [215, 45, 260, 91]}"
api_url='http://127.0.0.1:9898/slide/match/b64/json', resp.text='{"status": 200, "result": {"target_y": 45, "target": [215, 45, 260, 91]}, "msg": ""}'
api_url='http://127.0.0.1:9898/slide/compare/file', resp.text="{'target': [144, 76]}"
api_url='http://127.0.0.1:9898/slide/compare/file/json', resp.text='{"status": 200, "result": {"target": [144, 76]}, "msg": ""}'
api_url='http://127.0.0.1:9898/slide/compare/b64', resp.text="{'target': [144, 76]}"
api_url='http://127.0.0.1:9898/slide/compare/b64/json', resp.text='{"status": 200, "result": {"target": [144, 76]}, "msg": ""}'
2
3
4
5
6
7
8
9
10
11
12
13
14
# 3.3 破解hcaptcha验证码
# 3.3.1 hcaptcha-challenger简介
hcaptcha-challenger是一个处理 hCaptcha 验证码的库。该项目不依赖浏览器插件和第三方反验证码服务,通过机器学习的方式处理 hCaptcha 验证码。它采用 ResNet 和 YOLOv8 处理分类和检测问题,通过 ONNX 打包模型降低使用门槛,让用户可在没有 GPU 的设备上运行代码,且无需安装 PyTorch 等依赖,轻松破解hCaptcha 验证码。
- 项目地址:https://github.com/QIN2DIM/hcaptcha-challenger (opens new window)
- 项目文档:https://github.com/QIN2DIM/hcaptcha-challenger/wiki/Get-started-%E2%80%90-Library (opens new window)
- hcaptcha验证码测试:https://maximedrn.github.io/hcaptcha-solver-python-selenium/ (opens new window)
# 3.3.2 hcaptcha-challenger使用示例
拉取项目代码并安装依赖,基础环境要求Python 3.8+
$ git clone https://github.com/QIN2DIM/hcaptcha-challenger.git
切换到 examples 目录,运行 demo_undetected_playwright.py 程序。该代码使用了 Python3.10 的新特性 match 语法,如果不是在 Python3.10 下运行,将其改成 if-elif-else 语法即可。
# -*- coding: utf-8 -*-
from __future__ import annotations
import asyncio
import time
from pathlib import Path
from loguru import logger
from playwright.async_api import BrowserContext as ASyncContext
import hcaptcha_challenger as solver
from hcaptcha_challenger.agents import AgentT, Malenia
from hcaptcha_challenger.utils import SiteKey
# Init local-side of the ModelHub
solver.install(upgrade=True, flush_yolo=False)
# Save dataset to current working directory
tmp_dir = Path(__file__).parent.joinpath("tmp_dir")
user_data_dir = Path(__file__).parent.joinpath("user_data_dir")
context_dir = user_data_dir.joinpath("context")
record_dir = user_data_dir.joinpath("record")
record_har_path = record_dir.joinpath(f"eg-{int(time.time())}.har")
sitekey = SiteKey.user_easy
@logger.catch
async def hit_challenge(context: ASyncContext, times: int = 8):
page = context.pages[0]
agent = AgentT.from_page(page=page, tmp_dir=tmp_dir)
await page.goto(SiteKey.as_sitelink(sitekey))
await agent.handle_checkbox()
for pth in range(1, times):
result = await agent()
print(f">> {pth} - Challenge Result: {result}")
if result == agent.status.CHALLENGE_BACKCALL:
await page.wait_for_timeout(500)
fl = page.frame_locator(agent.HOOK_CHALLENGE)
await fl.locator("//div[@class='refresh button']").click()
elif result == agent.status.CHALLENGE_SUCCESS:
rqdata_path = agent.export_rq()
print(f"View RQdata path={rqdata_path}")
return
async def bytedance():
malenia = Malenia(
user_data_dir=context_dir, record_dir=record_dir, record_har_path=record_har_path
)
await malenia.execute(sequence=[hit_challenge], headless=False)
print(f"View record video path={record_dir}")
if __name__ == "__main__":
asyncio.run(bytedance())
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
运行效果如下:
# 4. Python爬虫实战
# 4.1 使用爬虫框架采集
# 4.1.1 采集政府网站数据
有些网站为了增加安全性或复杂性,会在发送请求前对请求参数进行加密。为了获取数据,爬虫需要正确地加密这些参数,这里使用node.js去逆向破解。
依赖安装:
$ npm install crypto-js
$ pip3 install pyExecJs
2
encrypt.js
var CryptoJS = require("crypto-js");
window = {
_d: 'V0ZhMGNiYTlhZDYyV0Y=',
_e: 'V0YxZjM3MmIxOWFlV0Y=',
_f: 'V0ZiYjE5MGRjMWFkZVdG'
}
var c = function(t) {
if (!t)
return t;
try {
var e = Buffer.from(t, "base64").toString("utf-8");
return e.replace(/(^WF)|(WF$)/g, "")
} catch (r) {
return console.log("解析dk碎片失败", r),
t
}
};
function a() {
var t = ""
, e = "undefined" !== typeof window._d ? window._d : ""
, r = "undefined" !== typeof window._e ? window._e : ""
, n = "undefined" !== typeof window._f ? window._f : "";
return e && (t += c(e)),
r && (t += c(r)),
n && (t += c(n)),
t
}
const secretKey = a();// 密钥
// 加密数据
function Encrypt(data){
var afterEncrypt = CryptoJS.DES.encrypt(data, CryptoJS.enc.Utf8.parse(secretKey), {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}).toString()
return afterEncrypt
}
// 解密
function Decrypt(data){
var afterDecrypt = CryptoJS.DES.decrypt(Buffer.from(data, 'base64').toString('base64'), CryptoJS.enc.Utf8.parse(secretKey), {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}).toString(CryptoJS.enc.Utf8);
return afterDecrypt
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
spider.py
# -*- coding: utf-8 -*-
import requests
import urllib
import execjs
import time
import json
with open('./encrypt.js', 'r', encoding='utf-8') as f:
js_text = f.read()
params = {
"flag": "",
"pageIndex": 1,
"pageRowNum": 10,
"itemType": 1,
"belongArea": 440000,
"decisionNum": "",
"legalDepName": "",
"caseName": "",
"time": int(time.time() * 1000),
"legalDepId": "70199ef9070048a1a4fa6dfa878ddc60"
}
fs = execjs.compile(js_text).call('Encrypt', json.dumps(params))
url = f"http://210.76.74.232/appr-law-datacenter-service/law/datacenter/result/list?pageIndex=1&pageRowNum=10&itemType=1&belongArea=440000&legalDepId=70199ef9070048a1a4fa6dfa878ddc60&decryptText={urllib.parse.quote(fs)}"
res = requests.get(url)
info = res.json()
print(info)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 4.1.2 批量下载抖音视频
批量爬取任意用户“作品”与“喜欢”里的视频,无水印。
项目使用:
Step1:打开抖音APP获取目标用户的分享短链接
- 自己:打开我的个人主页,点右上角的“...”——个人名片——右上角的转发箭头——复制链接
- 关注的用户:打开目标用户的个人主页,点右上角的“...”——右上角的转发箭头——复制链接
Step2:将上一步获取到的短链接复制到浏览器打开,从原始链接中获取目标用户的UID(即share/user/
后面的数字)
- 短链接:`https://v.douyin.com/bB4kuM/`
- 原始链接:`https://www.iesdouyin.com/share/user/97682711563?u_code=kak72ilk&sec_uid=MS4wLjABAAAAIJrPdreQJViuB5hb0K6slORPBCK6GqdatAKPVDeSwdI&utm_campaign=client_share&app=aweme&utm_medium=ios&tt_from=copy&utm_source=copy`
- UID:`97682711563`
2
3
Step3:运行douyin.py脚本,按要求填入信息即可。
注意事项:爬取后的结果可能存在无效视频。
产生原因:
原因1:未关闭私密权限,就爬取自己收藏的视频(打开我的个人主页,点右上角的“...”——设置——隐私设置——关闭“私密账号”)
原因2:爬取过于频繁,被服务器限制了
原因3:收藏的视频已被原作者删除
解决办法:查找并批量删除无效视频——在文件夹内按照文件大小排序,删除0kb的无效文件
# 4.1.3 采集链家贝壳房价数据
链家网和贝壳网房价爬虫,采集北京上海广州深圳等21个中国主要城市的房价数据(小区,二手房,出租房,新房)
# 4.2 使用八爪鱼采集器采集
非专业人士,采集少量数据,没有特殊需求,不需要持续采集的话,建议直接使用无代码采集器进行采集,学习成本更低,写得更快。
这里以 烟台市人民政府 (opens new window) 的通知公告为例,演示一下常规的列表翻页采集详情场景。
基本流程:首页配置采集链接——选择列表页链接元素——配置翻页元素——点击链接元素进入详情——选择详情页采集元素——开始采集——导出数据
免费版仅能使用本地采集,循环列表类型的采集任务可以使用加速模式(免费版每月仅能使用3次),加速模式的采集过程中会产生大量重复采集数据,但是它自带去重功能。
支持以下数据导出格式,如果需要别的格式可以自己写个python脚本去处理。
比如我需要将 json 转换成 jsonl 格式,去除换行符和制表符,校验采集元素
# -*- coding: utf-8 -*-
import os
import json
import re
# 将JSON转换为JSONL,并进行额外处理
def json_to_jsonl(json_file_path, jsonl_file_path):
with open(json_file_path, 'r') as f_in:
json_data = json.load(f_in)
with open(jsonl_file_path, 'w') as f_out:
for item in json_data:
# 去除换行符和制表符
for key in item.keys():
if isinstance(item[key], str):
item[key] = re.sub(r'\s+', ' ', item[key]).strip()
# 如果"title"或"content"为空,则跳过该条数据
if not item.get('title') or not item.get('content'):
continue
f_out.write(json.dumps(item, ensure_ascii=False))
f_out.write('\n')
# 深度遍历目录并处理文件
def deep_traverse_directory(root_dir, output_dir):
for root, dirs, files in os.walk(root_dir):
rel_path = os.path.relpath(root, root_dir)
output_root = os.path.join(output_dir, rel_path)
os.makedirs(output_root, exist_ok=True)
for name in files:
if name.endswith('.json'):
json_file_path = os.path.join(root, name)
jsonl_file_name = f"{os.path.splitext(name)[0]}.jsonl"
jsonl_file_path = os.path.join(output_root, jsonl_file_name)
json_to_jsonl(json_file_path, jsonl_file_path)
if __name__ == "__main__":
root_dir = "../input" # 替换为你要遍历的目录
output_dir = "./output" # 输出目录
os.makedirs(output_dir, exist_ok=True) # 创建输出目录
deep_traverse_directory(root_dir, output_dir)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
其他注意事项:
- 该工具的免费版就支持100个任务,已经爬完病导出数据的任务及时删除。
- 如果不影响采集内容的话,可以在设置里把“不加载网页图片”、“屏蔽网页广告”勾选上,这样可以加快页面访问速度,提高采集效率。
- 分页列表采集详情,第一个链接没有需要采集的数据,不适用于后面的链接,欲从后面的链接提取规则:先打开浏览模式,在地址栏输入一下目标链接,之后把浏览模式关了,再选择元素。
- 提取详情时把正文的每个段落都当作了一行,需要把它合并到一起:选择好字段之后,勾选上“同一字段的多行合并”。
# 5. 参考资料
[1] Scrapy 入门教程 from runoob (opens new window)
[2] Python爬虫代理IP池(proxy pool) from Github (opens new window)
[3] 爬虫法律专题 from 微信公众号 (opens new window)
[4] Python 抓取可用代理IP from CSDN (opens new window)
[5] python 自定义异常和主动抛出异常(raise)的操作 from 华为云 (opens new window)
[6] Python3网络爬虫实战_Github (opens new window)
[7] 链家网和贝壳网房价爬虫 from Github (opens new window)
[8] Playwright Test新一代Web自动化测试框架 from 稀土掘金 (opens new window)
[9] python——正则表达式(re模块)详解 from CSDN (opens new window)