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.
Files changed (54) hide show
  1. qcrawler-0.4.3/PKG-INFO +337 -0
  2. qcrawler-0.4.3/README.md +310 -0
  3. qcrawler-0.4.3/pyproject.toml +40 -0
  4. qcrawler-0.4.3/qcrawl/__init__.py +31 -0
  5. qcrawler-0.4.3/qcrawl/cli.py +461 -0
  6. qcrawler-0.4.3/qcrawl/context.py +53 -0
  7. qcrawler-0.4.3/qcrawl/datalayer/__init__.py +19 -0
  8. qcrawler-0.4.3/qcrawl/datalayer/database.py +172 -0
  9. qcrawler-0.4.3/qcrawl/datalayer/minio_client.py +349 -0
  10. qcrawler-0.4.3/qcrawl/datalayer/report_item.py +95 -0
  11. qcrawler-0.4.3/qcrawl/datalayer/reporter.py +352 -0
  12. qcrawler-0.4.3/qcrawl/debug.py +184 -0
  13. qcrawler-0.4.3/qcrawl/downloader/__init__.py +6 -0
  14. qcrawler-0.4.3/qcrawl/downloader/base.py +21 -0
  15. qcrawler-0.4.3/qcrawl/downloader/curl.py +124 -0
  16. qcrawler-0.4.3/qcrawl/downloader/request_go.py +129 -0
  17. qcrawler-0.4.3/qcrawl/downloader/requests_downloader.py +126 -0
  18. qcrawler-0.4.3/qcrawl/engine.py +488 -0
  19. qcrawler-0.4.3/qcrawl/enums.py +25 -0
  20. qcrawler-0.4.3/qcrawl/exceptions.py +33 -0
  21. qcrawler-0.4.3/qcrawl/middlewares/__init__.py +7 -0
  22. qcrawler-0.4.3/qcrawl/middlewares/base.py +47 -0
  23. qcrawler-0.4.3/qcrawl/middlewares/cookie.py +154 -0
  24. qcrawler-0.4.3/qcrawl/middlewares/log.py +71 -0
  25. qcrawler-0.4.3/qcrawl/middlewares/proxy.py +220 -0
  26. qcrawler-0.4.3/qcrawl/middlewares/ratelimit.py +149 -0
  27. qcrawler-0.4.3/qcrawl/middlewares/redirect.py +95 -0
  28. qcrawler-0.4.3/qcrawl/middlewares/retry.py +57 -0
  29. qcrawler-0.4.3/qcrawl/middlewares/timeout.py +34 -0
  30. qcrawler-0.4.3/qcrawl/middlewares/useragent.py +42 -0
  31. qcrawler-0.4.3/qcrawl/process_manager.py +207 -0
  32. qcrawler-0.4.3/qcrawl/request.py +34 -0
  33. qcrawler-0.4.3/qcrawl/request_item.py +22 -0
  34. qcrawler-0.4.3/qcrawl/response.py +69 -0
  35. qcrawler-0.4.3/qcrawl/scheduler.py +197 -0
  36. qcrawler-0.4.3/qcrawl/seed_queue.py +123 -0
  37. qcrawler-0.4.3/qcrawl/selector.py +22 -0
  38. qcrawler-0.4.3/qcrawl/settings.py +143 -0
  39. qcrawler-0.4.3/qcrawl/spider.py +245 -0
  40. qcrawler-0.4.3/qcrawl/utils/__init__.py +1 -0
  41. qcrawler-0.4.3/qcrawl/utils/fingerprint.py +29 -0
  42. qcrawler-0.4.3/qcrawl/utils/import_helper.py +33 -0
  43. qcrawler-0.4.3/qcrawl/utils/lock.py +137 -0
  44. qcrawler-0.4.3/qcrawl/utils/log.py +56 -0
  45. qcrawler-0.4.3/qcrawl/utils/metrics.py +221 -0
  46. qcrawler-0.4.3/qcrawl/utils/network.py +15 -0
  47. qcrawler-0.4.3/qcrawler.egg-info/PKG-INFO +337 -0
  48. qcrawler-0.4.3/qcrawler.egg-info/SOURCES.txt +52 -0
  49. qcrawler-0.4.3/qcrawler.egg-info/dependency_links.txt +1 -0
  50. qcrawler-0.4.3/qcrawler.egg-info/entry_points.txt +2 -0
  51. qcrawler-0.4.3/qcrawler.egg-info/requires.txt +15 -0
  52. qcrawler-0.4.3/qcrawler.egg-info/top_level.txt +1 -0
  53. qcrawler-0.4.3/setup.cfg +4 -0
  54. qcrawler-0.4.3/tests/test_core.py +289 -0
@@ -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
@@ -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*"]