qcrawler 0.4.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qcrawler-0.4.3/PKG-INFO +337 -0
- qcrawler-0.4.3/README.md +310 -0
- qcrawler-0.4.3/pyproject.toml +40 -0
- qcrawler-0.4.3/qcrawl/__init__.py +31 -0
- qcrawler-0.4.3/qcrawl/cli.py +461 -0
- qcrawler-0.4.3/qcrawl/context.py +53 -0
- qcrawler-0.4.3/qcrawl/datalayer/__init__.py +19 -0
- qcrawler-0.4.3/qcrawl/datalayer/database.py +172 -0
- qcrawler-0.4.3/qcrawl/datalayer/minio_client.py +349 -0
- qcrawler-0.4.3/qcrawl/datalayer/report_item.py +95 -0
- qcrawler-0.4.3/qcrawl/datalayer/reporter.py +352 -0
- qcrawler-0.4.3/qcrawl/debug.py +184 -0
- qcrawler-0.4.3/qcrawl/downloader/__init__.py +6 -0
- qcrawler-0.4.3/qcrawl/downloader/base.py +21 -0
- qcrawler-0.4.3/qcrawl/downloader/curl.py +124 -0
- qcrawler-0.4.3/qcrawl/downloader/request_go.py +129 -0
- qcrawler-0.4.3/qcrawl/downloader/requests_downloader.py +126 -0
- qcrawler-0.4.3/qcrawl/engine.py +488 -0
- qcrawler-0.4.3/qcrawl/enums.py +25 -0
- qcrawler-0.4.3/qcrawl/exceptions.py +33 -0
- qcrawler-0.4.3/qcrawl/middlewares/__init__.py +7 -0
- qcrawler-0.4.3/qcrawl/middlewares/base.py +47 -0
- qcrawler-0.4.3/qcrawl/middlewares/cookie.py +154 -0
- qcrawler-0.4.3/qcrawl/middlewares/log.py +71 -0
- qcrawler-0.4.3/qcrawl/middlewares/proxy.py +220 -0
- qcrawler-0.4.3/qcrawl/middlewares/ratelimit.py +149 -0
- qcrawler-0.4.3/qcrawl/middlewares/redirect.py +95 -0
- qcrawler-0.4.3/qcrawl/middlewares/retry.py +57 -0
- qcrawler-0.4.3/qcrawl/middlewares/timeout.py +34 -0
- qcrawler-0.4.3/qcrawl/middlewares/useragent.py +42 -0
- qcrawler-0.4.3/qcrawl/process_manager.py +207 -0
- qcrawler-0.4.3/qcrawl/request.py +34 -0
- qcrawler-0.4.3/qcrawl/request_item.py +22 -0
- qcrawler-0.4.3/qcrawl/response.py +69 -0
- qcrawler-0.4.3/qcrawl/scheduler.py +197 -0
- qcrawler-0.4.3/qcrawl/seed_queue.py +123 -0
- qcrawler-0.4.3/qcrawl/selector.py +22 -0
- qcrawler-0.4.3/qcrawl/settings.py +143 -0
- qcrawler-0.4.3/qcrawl/spider.py +245 -0
- qcrawler-0.4.3/qcrawl/utils/__init__.py +1 -0
- qcrawler-0.4.3/qcrawl/utils/fingerprint.py +29 -0
- qcrawler-0.4.3/qcrawl/utils/import_helper.py +33 -0
- qcrawler-0.4.3/qcrawl/utils/lock.py +137 -0
- qcrawler-0.4.3/qcrawl/utils/log.py +56 -0
- qcrawler-0.4.3/qcrawl/utils/metrics.py +221 -0
- qcrawler-0.4.3/qcrawl/utils/network.py +15 -0
- qcrawler-0.4.3/qcrawler.egg-info/PKG-INFO +337 -0
- qcrawler-0.4.3/qcrawler.egg-info/SOURCES.txt +52 -0
- qcrawler-0.4.3/qcrawler.egg-info/dependency_links.txt +1 -0
- qcrawler-0.4.3/qcrawler.egg-info/entry_points.txt +2 -0
- qcrawler-0.4.3/qcrawler.egg-info/requires.txt +15 -0
- qcrawler-0.4.3/qcrawler.egg-info/top_level.txt +1 -0
- qcrawler-0.4.3/setup.cfg +4 -0
- qcrawler-0.4.3/tests/test_core.py +289 -0
qcrawler-0.4.3/PKG-INFO
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qcrawler
|
|
3
|
+
Version: 0.4.3
|
|
4
|
+
Summary: 分布式 Python 爬虫框架
|
|
5
|
+
License: MIT
|
|
6
|
+
Classifier: Development Status :: 3 - Alpha
|
|
7
|
+
Classifier: Intended Audience :: Developers
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
|
|
12
|
+
Requires-Python: >=3.11
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: requests>=2.28
|
|
15
|
+
Requires-Dist: loguru>=0.7
|
|
16
|
+
Requires-Dist: parsel>=1.8
|
|
17
|
+
Requires-Dist: pyyaml>=6.0
|
|
18
|
+
Requires-Dist: pydantic>=2.0
|
|
19
|
+
Requires-Dist: redis>=4.0
|
|
20
|
+
Requires-Dist: sqlalchemy>=2.0
|
|
21
|
+
Requires-Dist: psycopg[binary]>=3.1
|
|
22
|
+
Requires-Dist: minio>=7.0
|
|
23
|
+
Provides-Extra: curl
|
|
24
|
+
Requires-Dist: curl_cffi>=0.5; extra == "curl"
|
|
25
|
+
Provides-Extra: all
|
|
26
|
+
Requires-Dist: qcrawler[curl]; extra == "all"
|
|
27
|
+
|
|
28
|
+
# QCrawl
|
|
29
|
+
|
|
30
|
+
分布式 Python 爬虫框架,开箱即用。
|
|
31
|
+
|
|
32
|
+
## 特性
|
|
33
|
+
|
|
34
|
+
- **极简开发** — 继承 `QSpider`,实现 `parse` 即可运行
|
|
35
|
+
- **分布式调度** — Redis 种子队列 + 分布式去重 + 多进程/多线程并发
|
|
36
|
+
- **多下载引擎** — requests / curl_cffi(TLS 指纹伪装)/ request-go(Go 子进程)
|
|
37
|
+
- **统一数据层** — Pydantic 模型 + PostgreSQL 批量写入 + MinIO 文件存储
|
|
38
|
+
- **生产级能力** — 代理池、域名限速、Cookie 管理、自动重试、死信队列
|
|
39
|
+
- **可观测指标** — Redis 指标采集(供爬虫管理平台消费)+ 结构化日志
|
|
40
|
+
- **本地调试** — `send_seed` 一条命令跑通全流程,无需 Redis/PG
|
|
41
|
+
|
|
42
|
+
## 安装
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# 从私有源安装(推荐)
|
|
46
|
+
pip install qcrawl --extra-index-url https://spidercoding-cn-hangzhou.devops.aliyuncs.com/packages/api/protocol/pypi/repo-mxqeh --trusted-host spidercoding-cn-hangzhou.devops.aliyuncs.com
|
|
47
|
+
|
|
48
|
+
# 可选:TLS 指纹伪装
|
|
49
|
+
pip install "qcrawl[curl]"
|
|
50
|
+
|
|
51
|
+
# 全部安装
|
|
52
|
+
pip install "qcrawl[all]"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
> 注:zsh 下方括号需加引号,如 `"qcrawl[all]"`
|
|
56
|
+
|
|
57
|
+
## 快速开始
|
|
58
|
+
|
|
59
|
+
### 1. 编写爬虫
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
# spiders/ithome/ithome_spider.py
|
|
63
|
+
from qcrawl import QSpider, Request
|
|
64
|
+
|
|
65
|
+
class IthomeSpider(QSpider):
|
|
66
|
+
name = "ithome"
|
|
67
|
+
data_type = "news"
|
|
68
|
+
table = "ithome_data"
|
|
69
|
+
|
|
70
|
+
def start_request(self, request_item):
|
|
71
|
+
url = request_item.seed_dict.get("url") or "https://www.ithome.com/"
|
|
72
|
+
yield Request(url, meta={"seed_id": request_item.seed_id})
|
|
73
|
+
|
|
74
|
+
def parse(self, response):
|
|
75
|
+
links = response.css("a[href*='/0/']::attr(href)").getall()
|
|
76
|
+
for link in links[:10]:
|
|
77
|
+
yield Request(response.urljoin(link), callback="parse_detail")
|
|
78
|
+
|
|
79
|
+
def parse_detail(self, response):
|
|
80
|
+
title = response.css("h1::text").get() or ""
|
|
81
|
+
if not title:
|
|
82
|
+
return
|
|
83
|
+
self.report_data({
|
|
84
|
+
"data_type": self.data_type,
|
|
85
|
+
"url": response.url,
|
|
86
|
+
"ext": {"title": title.strip()},
|
|
87
|
+
})
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 2. 本地调试(无需 Redis/PG)
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from qcrawl import send_seed
|
|
94
|
+
from spiders.ithome.ithome_spider import IthomeSpider
|
|
95
|
+
|
|
96
|
+
send_seed(IthomeSpider, {"url": "https://www.ithome.com/"})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 3. CLI 启动
|
|
100
|
+
|
|
101
|
+
安装后直接使用 `qcrawl` 命令(无需 main.py)。示例爬虫已拆分至独立项目 **qcrawl_crawler**(爬虫业务仓库),切换到该目录即可体验:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
cd qcrawl_crawler
|
|
105
|
+
|
|
106
|
+
# 本地模式
|
|
107
|
+
qcrawl -s spiders.ithome.ithome_spider.IthomeSpider --local
|
|
108
|
+
|
|
109
|
+
# 分布式模式(读取 settings.yaml)
|
|
110
|
+
qcrawl -s spiders.ithome.ithome_spider.IthomeSpider --env dev --project_id my_project
|
|
111
|
+
|
|
112
|
+
# 手动指定连接
|
|
113
|
+
qcrawl -s spiders.ithome.ithome_spider.IthomeSpider \
|
|
114
|
+
--project_id my_project \
|
|
115
|
+
--redis_url redis://127.0.0.1:6379/0 \
|
|
116
|
+
--db_url postgresql+psycopg://user:pass@localhost:5432/crawl \
|
|
117
|
+
--processes 2 \
|
|
118
|
+
--threads 8
|
|
119
|
+
|
|
120
|
+
# 使用 curl_cffi 绕过 TLS 指纹检测
|
|
121
|
+
qcrawl -s spiders.ithome.ithome_spider.IthomeSpider --http_client curl --local
|
|
122
|
+
|
|
123
|
+
# 导入种子文件
|
|
124
|
+
qcrawl -s spiders.ithome.ithome_spider.IthomeSpider --seeds_file seeds.jsonl --env dev
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## CLI 参数
|
|
128
|
+
|
|
129
|
+
| 参数 | 说明 | 默认值 |
|
|
130
|
+
|------|------|--------|
|
|
131
|
+
| `-s, --spider` | 爬虫类路径(必填) | — |
|
|
132
|
+
| `--env` | 运行环境 dev/prod | dev |
|
|
133
|
+
| `--project_id` | 项目 ID | — |
|
|
134
|
+
| `--processes` | 工作进程数 | 1 |
|
|
135
|
+
| `--threads` | 每进程并发线程数 | 4 |
|
|
136
|
+
| `--proxy_type` | 代理模式 off/static/api/redis | off |
|
|
137
|
+
| `--proxy_url` | 代理地址 | — |
|
|
138
|
+
| `--timeout` | 请求超时秒数 | 15 |
|
|
139
|
+
| `--retry` | 重试次数 | 3 |
|
|
140
|
+
| `--delay` | 请求间隔秒数 | 0 |
|
|
141
|
+
| `--http_client` | HTTP 引擎 requests/curl/request-go | requests |
|
|
142
|
+
| `--redis_url` | Redis 连接串 | — |
|
|
143
|
+
| `--db_url` | PostgreSQL 连接串 | — |
|
|
144
|
+
| `--seeds_file` | 种子文件路径 (JSON/JSONL) | — |
|
|
145
|
+
| `--local` | 本地模式(无需 Redis/PG) | — |
|
|
146
|
+
| `--migrate` | 仅执行表结构迁移 | — |
|
|
147
|
+
|
|
148
|
+
## 架构
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
┌─────────────────────────────────────────────────┐
|
|
152
|
+
│ CLI (qcrawl 命令) │
|
|
153
|
+
├─────────────────────────────────────────────────┤
|
|
154
|
+
│ Engine(引擎) │
|
|
155
|
+
│ ├── Scheduler(Redis 种子消费 + 去重) │
|
|
156
|
+
│ ├── Middleware Chain(中间件链) │
|
|
157
|
+
│ │ Log → UA → Proxy → Retry → Cookie │
|
|
158
|
+
│ │ → Redirect → Timeout → RateLimit │
|
|
159
|
+
│ ├── Downloader(下载器) │
|
|
160
|
+
│ │ requests / curl_cffi / request-go │
|
|
161
|
+
│ └── Worker Thread Pool(并发执行) │
|
|
162
|
+
├─────────────────────────────────────────────────┤
|
|
163
|
+
│ DataLayer(数据层) │
|
|
164
|
+
│ ├── Reporter → PostgreSQL 批量写入 │
|
|
165
|
+
│ ├── MinIO → 文件存储 │
|
|
166
|
+
│ └── MetricsCollector → Redis 指标采集 │
|
|
167
|
+
└─────────────────────────────────────────────────┘
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## 配置优先级
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
命令行参数 > 爬虫类 settings > settings.yaml > 框架默认值
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### settings.yaml 示例
|
|
177
|
+
|
|
178
|
+
```yaml
|
|
179
|
+
common:
|
|
180
|
+
threads: 4
|
|
181
|
+
timeout: 15
|
|
182
|
+
retry_times: 3
|
|
183
|
+
|
|
184
|
+
dev:
|
|
185
|
+
redis_url: redis://127.0.0.1:6379/0
|
|
186
|
+
log_level: DEBUG
|
|
187
|
+
|
|
188
|
+
prod:
|
|
189
|
+
redis_url: ${REDIS_URL}
|
|
190
|
+
db_url: ${DB_URL}
|
|
191
|
+
threads: 16
|
|
192
|
+
processes: 4
|
|
193
|
+
log_level: INFO
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## 数据上报
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
from qcrawl import ReportItem
|
|
200
|
+
from datetime import date
|
|
201
|
+
|
|
202
|
+
# 方式一:dict(简单场景)
|
|
203
|
+
self.report_data({
|
|
204
|
+
"data_type": "news",
|
|
205
|
+
"url": response.url,
|
|
206
|
+
"dt": date.today().isoformat(), # 自定义日期标识
|
|
207
|
+
"ext": {"title": "hello"},
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
# 方式二:ReportItem(推荐,有类型提示)
|
|
211
|
+
self.report_data(ReportItem(
|
|
212
|
+
data_type="news",
|
|
213
|
+
url=response.url,
|
|
214
|
+
dt="2026-07-30",
|
|
215
|
+
ext={"title": "hello", "content": "..."},
|
|
216
|
+
))
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
框架自动注入 `project_id`、`spider_name`、`seed_id`、`pod_ip`、`crawl_time` 字段。
|
|
220
|
+
|
|
221
|
+
**字段说明:**
|
|
222
|
+
|
|
223
|
+
| 字段 | 类型 | 来源 | 说明 |
|
|
224
|
+
|------|------|------|------|
|
|
225
|
+
| `project_id` | str | 框架注入 | 项目 ID |
|
|
226
|
+
| `spider_name` | str | 框架注入 | 爬虫名称 |
|
|
227
|
+
| `seed_id` | str | 框架注入 | 种子 ID(链路追踪) |
|
|
228
|
+
| `pod_ip` | str | 框架注入 | 执行节点 IP |
|
|
229
|
+
| `crawl_time` | datetime | 框架注入 | 抓取时间(UTC) |
|
|
230
|
+
| `data_type` | str | 开发者填 | 数据类型(news/article/product 等) |
|
|
231
|
+
| `dt` | str | 开发者填 | 自定义日期标识(如 "2026-07-30") |
|
|
232
|
+
| `url` | str | 开发者填 | 数据来源 URL |
|
|
233
|
+
| `s3_addr` | list[str] | 开发者填 | MinIO 文件地址列表 |
|
|
234
|
+
| `ext` | JSONB | 开发者填 | 站点特有字段,自由写入 |
|
|
235
|
+
|
|
236
|
+
## 文件上传
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
def parse_detail(self, response):
|
|
240
|
+
img_bytes = self.download(response.css("img::attr(src)").get())
|
|
241
|
+
s3_url = self.upload_file(img_bytes, key="images/abc.png", content_type="image/png")
|
|
242
|
+
self.report_data({"data_type": "image", "s3_addr": [s3_url], "ext": {}})
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## 监控指标
|
|
246
|
+
|
|
247
|
+
QCrawl 运行时将指标写入 Redis(`qcrawl:metrics:{project}:{spider}:*`),不包含内置监控 API 与 Dashboard。可视化展示、死信重放等运维能力由**爬虫管理平台**(独立项目)直接读取 Redis / PostgreSQL 承接,详见 DESIGN.md 第 17 章。
|
|
248
|
+
|
|
249
|
+
## 新项目使用
|
|
250
|
+
|
|
251
|
+
安装 qcrawl 后,你的爬虫项目只需如下结构:
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
my_crawler/
|
|
255
|
+
├── settings.yaml # 环境配置(redis/pg/minio)
|
|
256
|
+
└── spiders/
|
|
257
|
+
├── __init__.py
|
|
258
|
+
└── ithome/
|
|
259
|
+
├── __init__.py
|
|
260
|
+
└── ithome_spider.py
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
无需 `main.py`,直接在项目根目录执行:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
cd my_crawler
|
|
267
|
+
qcrawl -s spiders.ithome.ithome_spider.IthomeSpider --env dev --project_id my_project
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## 开发
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
# 克隆 & 安装开发依赖
|
|
274
|
+
git clone https://gitee.com/yezhian/qcrawl.git && cd qcrawl
|
|
275
|
+
python -m venv .venv && source .venv/bin/activate
|
|
276
|
+
pip install -e ".[all]"
|
|
277
|
+
|
|
278
|
+
# 运行测试
|
|
279
|
+
pytest tests/ -v
|
|
280
|
+
|
|
281
|
+
# 构建发布
|
|
282
|
+
pip install build twine
|
|
283
|
+
python -m build
|
|
284
|
+
twine upload --repository packages-pypi dist/*
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## 项目结构
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
qcrawl/
|
|
291
|
+
├── qcrawl/ # 框架源码包
|
|
292
|
+
│ ├── __init__.py # 公共 API 导出
|
|
293
|
+
│ ├── cli.py # CLI 入口(console_scripts)
|
|
294
|
+
│ ├── engine.py # 引擎(线程池 + 中间件链)
|
|
295
|
+
│ ├── spider.py # QSpider 基类
|
|
296
|
+
│ ├── scheduler.py # Redis 种子调度 + 去重
|
|
297
|
+
│ ├── request.py # Request 模型
|
|
298
|
+
│ ├── response.py # Response 封装
|
|
299
|
+
│ ├── request_item.py # 种子封装
|
|
300
|
+
│ ├── settings.py # 四级配置系统
|
|
301
|
+
│ ├── context.py # 线程上下文(seed_id 传递)
|
|
302
|
+
│ ├── debug.py # send_seed 本地调试
|
|
303
|
+
│ ├── enums.py # 框架枚举(ProjectId / DataType)
|
|
304
|
+
│ ├── process_manager.py # 多进程管理
|
|
305
|
+
│ ├── datalayer/ # 数据层
|
|
306
|
+
│ │ ├── database.py # 数据库连接 + 自动建表
|
|
307
|
+
│ │ ├── report_item.py # ReportItem 统一模型
|
|
308
|
+
│ │ ├── reporter.py # PG 批量写入
|
|
309
|
+
│ │ └── minio_client.py # MinIO 文件存储
|
|
310
|
+
│ ├── downloader/ # 下载器
|
|
311
|
+
│ │ ├── requests_downloader.py # requests(默认)
|
|
312
|
+
│ │ ├── curl.py # curl_cffi(TLS 伪装)
|
|
313
|
+
│ │ └── request_go.py # Go 子进程
|
|
314
|
+
│ ├── middlewares/ # 中间件
|
|
315
|
+
│ │ ├── log.py # 日志(priority=50)
|
|
316
|
+
│ │ ├── useragent.py # UA 轮换(100)
|
|
317
|
+
│ │ ├── proxy.py # 代理分配(200)
|
|
318
|
+
│ │ ├── retry.py # 自动重试(300)
|
|
319
|
+
│ │ ├── cookie.py # Cookie 管理(400)
|
|
320
|
+
│ │ ├── redirect.py # 重定向跟随(500)
|
|
321
|
+
│ │ ├── timeout.py # 超时控制(600)
|
|
322
|
+
│ │ └── ratelimit.py # 域名限速(700)
|
|
323
|
+
│ ├── seed_queue.py # Redis 种子队列(FIFO + 死信)
|
|
324
|
+
│ └── utils/ # 工具
|
|
325
|
+
│ ├── fingerprint.py # URL 去重指纹
|
|
326
|
+
│ ├── lock.py # 分布式锁
|
|
327
|
+
│ ├── metrics.py # 指标采集器
|
|
328
|
+
│ ├── log.py # 日志初始化
|
|
329
|
+
│ └── network.py # 网络工具
|
|
330
|
+
└── tests/ # 单元测试
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
> 爬虫业务代码(spiders/settings.yaml/推种子脚本)已独立为 **qcrawl_crawler** 仓库维护,本仓库仅保留框架本体。
|
|
334
|
+
|
|
335
|
+
## License
|
|
336
|
+
|
|
337
|
+
MIT
|
qcrawler-0.4.3/README.md
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# QCrawl
|
|
2
|
+
|
|
3
|
+
分布式 Python 爬虫框架,开箱即用。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- **极简开发** — 继承 `QSpider`,实现 `parse` 即可运行
|
|
8
|
+
- **分布式调度** — Redis 种子队列 + 分布式去重 + 多进程/多线程并发
|
|
9
|
+
- **多下载引擎** — requests / curl_cffi(TLS 指纹伪装)/ request-go(Go 子进程)
|
|
10
|
+
- **统一数据层** — Pydantic 模型 + PostgreSQL 批量写入 + MinIO 文件存储
|
|
11
|
+
- **生产级能力** — 代理池、域名限速、Cookie 管理、自动重试、死信队列
|
|
12
|
+
- **可观测指标** — Redis 指标采集(供爬虫管理平台消费)+ 结构化日志
|
|
13
|
+
- **本地调试** — `send_seed` 一条命令跑通全流程,无需 Redis/PG
|
|
14
|
+
|
|
15
|
+
## 安装
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# 从私有源安装(推荐)
|
|
19
|
+
pip install qcrawl --extra-index-url https://spidercoding-cn-hangzhou.devops.aliyuncs.com/packages/api/protocol/pypi/repo-mxqeh --trusted-host spidercoding-cn-hangzhou.devops.aliyuncs.com
|
|
20
|
+
|
|
21
|
+
# 可选:TLS 指纹伪装
|
|
22
|
+
pip install "qcrawl[curl]"
|
|
23
|
+
|
|
24
|
+
# 全部安装
|
|
25
|
+
pip install "qcrawl[all]"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
> 注:zsh 下方括号需加引号,如 `"qcrawl[all]"`
|
|
29
|
+
|
|
30
|
+
## 快速开始
|
|
31
|
+
|
|
32
|
+
### 1. 编写爬虫
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
# spiders/ithome/ithome_spider.py
|
|
36
|
+
from qcrawl import QSpider, Request
|
|
37
|
+
|
|
38
|
+
class IthomeSpider(QSpider):
|
|
39
|
+
name = "ithome"
|
|
40
|
+
data_type = "news"
|
|
41
|
+
table = "ithome_data"
|
|
42
|
+
|
|
43
|
+
def start_request(self, request_item):
|
|
44
|
+
url = request_item.seed_dict.get("url") or "https://www.ithome.com/"
|
|
45
|
+
yield Request(url, meta={"seed_id": request_item.seed_id})
|
|
46
|
+
|
|
47
|
+
def parse(self, response):
|
|
48
|
+
links = response.css("a[href*='/0/']::attr(href)").getall()
|
|
49
|
+
for link in links[:10]:
|
|
50
|
+
yield Request(response.urljoin(link), callback="parse_detail")
|
|
51
|
+
|
|
52
|
+
def parse_detail(self, response):
|
|
53
|
+
title = response.css("h1::text").get() or ""
|
|
54
|
+
if not title:
|
|
55
|
+
return
|
|
56
|
+
self.report_data({
|
|
57
|
+
"data_type": self.data_type,
|
|
58
|
+
"url": response.url,
|
|
59
|
+
"ext": {"title": title.strip()},
|
|
60
|
+
})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 2. 本地调试(无需 Redis/PG)
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from qcrawl import send_seed
|
|
67
|
+
from spiders.ithome.ithome_spider import IthomeSpider
|
|
68
|
+
|
|
69
|
+
send_seed(IthomeSpider, {"url": "https://www.ithome.com/"})
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 3. CLI 启动
|
|
73
|
+
|
|
74
|
+
安装后直接使用 `qcrawl` 命令(无需 main.py)。示例爬虫已拆分至独立项目 **qcrawl_crawler**(爬虫业务仓库),切换到该目录即可体验:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
cd qcrawl_crawler
|
|
78
|
+
|
|
79
|
+
# 本地模式
|
|
80
|
+
qcrawl -s spiders.ithome.ithome_spider.IthomeSpider --local
|
|
81
|
+
|
|
82
|
+
# 分布式模式(读取 settings.yaml)
|
|
83
|
+
qcrawl -s spiders.ithome.ithome_spider.IthomeSpider --env dev --project_id my_project
|
|
84
|
+
|
|
85
|
+
# 手动指定连接
|
|
86
|
+
qcrawl -s spiders.ithome.ithome_spider.IthomeSpider \
|
|
87
|
+
--project_id my_project \
|
|
88
|
+
--redis_url redis://127.0.0.1:6379/0 \
|
|
89
|
+
--db_url postgresql+psycopg://user:pass@localhost:5432/crawl \
|
|
90
|
+
--processes 2 \
|
|
91
|
+
--threads 8
|
|
92
|
+
|
|
93
|
+
# 使用 curl_cffi 绕过 TLS 指纹检测
|
|
94
|
+
qcrawl -s spiders.ithome.ithome_spider.IthomeSpider --http_client curl --local
|
|
95
|
+
|
|
96
|
+
# 导入种子文件
|
|
97
|
+
qcrawl -s spiders.ithome.ithome_spider.IthomeSpider --seeds_file seeds.jsonl --env dev
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## CLI 参数
|
|
101
|
+
|
|
102
|
+
| 参数 | 说明 | 默认值 |
|
|
103
|
+
|------|------|--------|
|
|
104
|
+
| `-s, --spider` | 爬虫类路径(必填) | — |
|
|
105
|
+
| `--env` | 运行环境 dev/prod | dev |
|
|
106
|
+
| `--project_id` | 项目 ID | — |
|
|
107
|
+
| `--processes` | 工作进程数 | 1 |
|
|
108
|
+
| `--threads` | 每进程并发线程数 | 4 |
|
|
109
|
+
| `--proxy_type` | 代理模式 off/static/api/redis | off |
|
|
110
|
+
| `--proxy_url` | 代理地址 | — |
|
|
111
|
+
| `--timeout` | 请求超时秒数 | 15 |
|
|
112
|
+
| `--retry` | 重试次数 | 3 |
|
|
113
|
+
| `--delay` | 请求间隔秒数 | 0 |
|
|
114
|
+
| `--http_client` | HTTP 引擎 requests/curl/request-go | requests |
|
|
115
|
+
| `--redis_url` | Redis 连接串 | — |
|
|
116
|
+
| `--db_url` | PostgreSQL 连接串 | — |
|
|
117
|
+
| `--seeds_file` | 种子文件路径 (JSON/JSONL) | — |
|
|
118
|
+
| `--local` | 本地模式(无需 Redis/PG) | — |
|
|
119
|
+
| `--migrate` | 仅执行表结构迁移 | — |
|
|
120
|
+
|
|
121
|
+
## 架构
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
┌─────────────────────────────────────────────────┐
|
|
125
|
+
│ CLI (qcrawl 命令) │
|
|
126
|
+
├─────────────────────────────────────────────────┤
|
|
127
|
+
│ Engine(引擎) │
|
|
128
|
+
│ ├── Scheduler(Redis 种子消费 + 去重) │
|
|
129
|
+
│ ├── Middleware Chain(中间件链) │
|
|
130
|
+
│ │ Log → UA → Proxy → Retry → Cookie │
|
|
131
|
+
│ │ → Redirect → Timeout → RateLimit │
|
|
132
|
+
│ ├── Downloader(下载器) │
|
|
133
|
+
│ │ requests / curl_cffi / request-go │
|
|
134
|
+
│ └── Worker Thread Pool(并发执行) │
|
|
135
|
+
├─────────────────────────────────────────────────┤
|
|
136
|
+
│ DataLayer(数据层) │
|
|
137
|
+
│ ├── Reporter → PostgreSQL 批量写入 │
|
|
138
|
+
│ ├── MinIO → 文件存储 │
|
|
139
|
+
│ └── MetricsCollector → Redis 指标采集 │
|
|
140
|
+
└─────────────────────────────────────────────────┘
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## 配置优先级
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
命令行参数 > 爬虫类 settings > settings.yaml > 框架默认值
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### settings.yaml 示例
|
|
150
|
+
|
|
151
|
+
```yaml
|
|
152
|
+
common:
|
|
153
|
+
threads: 4
|
|
154
|
+
timeout: 15
|
|
155
|
+
retry_times: 3
|
|
156
|
+
|
|
157
|
+
dev:
|
|
158
|
+
redis_url: redis://127.0.0.1:6379/0
|
|
159
|
+
log_level: DEBUG
|
|
160
|
+
|
|
161
|
+
prod:
|
|
162
|
+
redis_url: ${REDIS_URL}
|
|
163
|
+
db_url: ${DB_URL}
|
|
164
|
+
threads: 16
|
|
165
|
+
processes: 4
|
|
166
|
+
log_level: INFO
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## 数据上报
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from qcrawl import ReportItem
|
|
173
|
+
from datetime import date
|
|
174
|
+
|
|
175
|
+
# 方式一:dict(简单场景)
|
|
176
|
+
self.report_data({
|
|
177
|
+
"data_type": "news",
|
|
178
|
+
"url": response.url,
|
|
179
|
+
"dt": date.today().isoformat(), # 自定义日期标识
|
|
180
|
+
"ext": {"title": "hello"},
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
# 方式二:ReportItem(推荐,有类型提示)
|
|
184
|
+
self.report_data(ReportItem(
|
|
185
|
+
data_type="news",
|
|
186
|
+
url=response.url,
|
|
187
|
+
dt="2026-07-30",
|
|
188
|
+
ext={"title": "hello", "content": "..."},
|
|
189
|
+
))
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
框架自动注入 `project_id`、`spider_name`、`seed_id`、`pod_ip`、`crawl_time` 字段。
|
|
193
|
+
|
|
194
|
+
**字段说明:**
|
|
195
|
+
|
|
196
|
+
| 字段 | 类型 | 来源 | 说明 |
|
|
197
|
+
|------|------|------|------|
|
|
198
|
+
| `project_id` | str | 框架注入 | 项目 ID |
|
|
199
|
+
| `spider_name` | str | 框架注入 | 爬虫名称 |
|
|
200
|
+
| `seed_id` | str | 框架注入 | 种子 ID(链路追踪) |
|
|
201
|
+
| `pod_ip` | str | 框架注入 | 执行节点 IP |
|
|
202
|
+
| `crawl_time` | datetime | 框架注入 | 抓取时间(UTC) |
|
|
203
|
+
| `data_type` | str | 开发者填 | 数据类型(news/article/product 等) |
|
|
204
|
+
| `dt` | str | 开发者填 | 自定义日期标识(如 "2026-07-30") |
|
|
205
|
+
| `url` | str | 开发者填 | 数据来源 URL |
|
|
206
|
+
| `s3_addr` | list[str] | 开发者填 | MinIO 文件地址列表 |
|
|
207
|
+
| `ext` | JSONB | 开发者填 | 站点特有字段,自由写入 |
|
|
208
|
+
|
|
209
|
+
## 文件上传
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
def parse_detail(self, response):
|
|
213
|
+
img_bytes = self.download(response.css("img::attr(src)").get())
|
|
214
|
+
s3_url = self.upload_file(img_bytes, key="images/abc.png", content_type="image/png")
|
|
215
|
+
self.report_data({"data_type": "image", "s3_addr": [s3_url], "ext": {}})
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## 监控指标
|
|
219
|
+
|
|
220
|
+
QCrawl 运行时将指标写入 Redis(`qcrawl:metrics:{project}:{spider}:*`),不包含内置监控 API 与 Dashboard。可视化展示、死信重放等运维能力由**爬虫管理平台**(独立项目)直接读取 Redis / PostgreSQL 承接,详见 DESIGN.md 第 17 章。
|
|
221
|
+
|
|
222
|
+
## 新项目使用
|
|
223
|
+
|
|
224
|
+
安装 qcrawl 后,你的爬虫项目只需如下结构:
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
my_crawler/
|
|
228
|
+
├── settings.yaml # 环境配置(redis/pg/minio)
|
|
229
|
+
└── spiders/
|
|
230
|
+
├── __init__.py
|
|
231
|
+
└── ithome/
|
|
232
|
+
├── __init__.py
|
|
233
|
+
└── ithome_spider.py
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
无需 `main.py`,直接在项目根目录执行:
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
cd my_crawler
|
|
240
|
+
qcrawl -s spiders.ithome.ithome_spider.IthomeSpider --env dev --project_id my_project
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## 开发
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
# 克隆 & 安装开发依赖
|
|
247
|
+
git clone https://gitee.com/yezhian/qcrawl.git && cd qcrawl
|
|
248
|
+
python -m venv .venv && source .venv/bin/activate
|
|
249
|
+
pip install -e ".[all]"
|
|
250
|
+
|
|
251
|
+
# 运行测试
|
|
252
|
+
pytest tests/ -v
|
|
253
|
+
|
|
254
|
+
# 构建发布
|
|
255
|
+
pip install build twine
|
|
256
|
+
python -m build
|
|
257
|
+
twine upload --repository packages-pypi dist/*
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## 项目结构
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
qcrawl/
|
|
264
|
+
├── qcrawl/ # 框架源码包
|
|
265
|
+
│ ├── __init__.py # 公共 API 导出
|
|
266
|
+
│ ├── cli.py # CLI 入口(console_scripts)
|
|
267
|
+
│ ├── engine.py # 引擎(线程池 + 中间件链)
|
|
268
|
+
│ ├── spider.py # QSpider 基类
|
|
269
|
+
│ ├── scheduler.py # Redis 种子调度 + 去重
|
|
270
|
+
│ ├── request.py # Request 模型
|
|
271
|
+
│ ├── response.py # Response 封装
|
|
272
|
+
│ ├── request_item.py # 种子封装
|
|
273
|
+
│ ├── settings.py # 四级配置系统
|
|
274
|
+
│ ├── context.py # 线程上下文(seed_id 传递)
|
|
275
|
+
│ ├── debug.py # send_seed 本地调试
|
|
276
|
+
│ ├── enums.py # 框架枚举(ProjectId / DataType)
|
|
277
|
+
│ ├── process_manager.py # 多进程管理
|
|
278
|
+
│ ├── datalayer/ # 数据层
|
|
279
|
+
│ │ ├── database.py # 数据库连接 + 自动建表
|
|
280
|
+
│ │ ├── report_item.py # ReportItem 统一模型
|
|
281
|
+
│ │ ├── reporter.py # PG 批量写入
|
|
282
|
+
│ │ └── minio_client.py # MinIO 文件存储
|
|
283
|
+
│ ├── downloader/ # 下载器
|
|
284
|
+
│ │ ├── requests_downloader.py # requests(默认)
|
|
285
|
+
│ │ ├── curl.py # curl_cffi(TLS 伪装)
|
|
286
|
+
│ │ └── request_go.py # Go 子进程
|
|
287
|
+
│ ├── middlewares/ # 中间件
|
|
288
|
+
│ │ ├── log.py # 日志(priority=50)
|
|
289
|
+
│ │ ├── useragent.py # UA 轮换(100)
|
|
290
|
+
│ │ ├── proxy.py # 代理分配(200)
|
|
291
|
+
│ │ ├── retry.py # 自动重试(300)
|
|
292
|
+
│ │ ├── cookie.py # Cookie 管理(400)
|
|
293
|
+
│ │ ├── redirect.py # 重定向跟随(500)
|
|
294
|
+
│ │ ├── timeout.py # 超时控制(600)
|
|
295
|
+
│ │ └── ratelimit.py # 域名限速(700)
|
|
296
|
+
│ ├── seed_queue.py # Redis 种子队列(FIFO + 死信)
|
|
297
|
+
│ └── utils/ # 工具
|
|
298
|
+
│ ├── fingerprint.py # URL 去重指纹
|
|
299
|
+
│ ├── lock.py # 分布式锁
|
|
300
|
+
│ ├── metrics.py # 指标采集器
|
|
301
|
+
│ ├── log.py # 日志初始化
|
|
302
|
+
│ └── network.py # 网络工具
|
|
303
|
+
└── tests/ # 单元测试
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
> 爬虫业务代码(spiders/settings.yaml/推种子脚本)已独立为 **qcrawl_crawler** 仓库维护,本仓库仅保留框架本体。
|
|
307
|
+
|
|
308
|
+
## License
|
|
309
|
+
|
|
310
|
+
MIT
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "qcrawler"
|
|
3
|
+
version = "0.4.3"
|
|
4
|
+
description = "分布式 Python 爬虫框架"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = {text = "MIT"}
|
|
7
|
+
requires-python = ">=3.11"
|
|
8
|
+
classifiers = [
|
|
9
|
+
"Development Status :: 3 - Alpha",
|
|
10
|
+
"Intended Audience :: Developers",
|
|
11
|
+
"Programming Language :: Python :: 3.11",
|
|
12
|
+
"Programming Language :: Python :: 3.12",
|
|
13
|
+
"Programming Language :: Python :: 3.13",
|
|
14
|
+
"Topic :: Internet :: WWW/HTTP :: Indexing/Search",
|
|
15
|
+
]
|
|
16
|
+
dependencies = [
|
|
17
|
+
"requests >= 2.28",
|
|
18
|
+
"loguru >= 0.7",
|
|
19
|
+
"parsel >= 1.8",
|
|
20
|
+
"pyyaml >= 6.0",
|
|
21
|
+
"pydantic >= 2.0",
|
|
22
|
+
"redis >= 4.0",
|
|
23
|
+
"sqlalchemy >= 2.0",
|
|
24
|
+
"psycopg[binary] >= 3.1",
|
|
25
|
+
"minio >= 7.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
curl = ["curl_cffi >= 0.5"]
|
|
30
|
+
all = ["qcrawler[curl]"]
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
qcrawl = "qcrawl.cli:main"
|
|
34
|
+
|
|
35
|
+
[build-system]
|
|
36
|
+
requires = ["setuptools >= 68.0"]
|
|
37
|
+
build-backend = "setuptools.build_meta"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
include = ["qcrawl*"]
|