crawlo 1.4.5__py3-none-any.whl → 1.4.6__py3-none-any.whl

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.

Potentially problematic release.


This version of crawlo might be problematic. Click here for more details.

Files changed (44) hide show
  1. crawlo/__version__.py +1 -1
  2. crawlo/downloader/cffi_downloader.py +3 -1
  3. crawlo/middleware/proxy.py +171 -348
  4. crawlo/pipelines/mysql_pipeline.py +339 -188
  5. crawlo/settings/default_settings.py +38 -30
  6. crawlo/stats_collector.py +10 -1
  7. crawlo/templates/project/settings.py.tmpl +10 -55
  8. crawlo/templates/project/settings_distributed.py.tmpl +20 -22
  9. crawlo/templates/project/settings_gentle.py.tmpl +5 -0
  10. crawlo/templates/project/settings_high_performance.py.tmpl +5 -0
  11. crawlo/templates/project/settings_minimal.py.tmpl +25 -1
  12. crawlo/templates/project/settings_simple.py.tmpl +5 -0
  13. crawlo/templates/run.py.tmpl +1 -8
  14. crawlo/templates/spider/spider.py.tmpl +5 -108
  15. crawlo/utils/db_helper.py +11 -5
  16. {crawlo-1.4.5.dist-info → crawlo-1.4.6.dist-info}/METADATA +1 -1
  17. {crawlo-1.4.5.dist-info → crawlo-1.4.6.dist-info}/RECORD +43 -29
  18. tests/authenticated_proxy_example.py +10 -6
  19. tests/explain_mysql_update_behavior.py +77 -0
  20. tests/simulate_mysql_update_test.py +140 -0
  21. tests/test_asyncmy_usage.py +57 -0
  22. tests/test_crawlo_proxy_integration.py +8 -2
  23. tests/test_downloader_proxy_compatibility.py +24 -20
  24. tests/test_mysql_pipeline_config.py +165 -0
  25. tests/test_mysql_pipeline_error.py +99 -0
  26. tests/test_mysql_pipeline_init_log.py +83 -0
  27. tests/test_mysql_pipeline_integration.py +133 -0
  28. tests/test_mysql_pipeline_refactor.py +144 -0
  29. tests/test_mysql_pipeline_refactor_simple.py +86 -0
  30. tests/test_mysql_pipeline_robustness.py +196 -0
  31. tests/test_mysql_pipeline_types.py +89 -0
  32. tests/test_mysql_update_columns.py +94 -0
  33. tests/test_proxy_middleware.py +104 -8
  34. tests/test_proxy_middleware_enhanced.py +1 -5
  35. tests/test_proxy_middleware_integration.py +7 -2
  36. tests/test_proxy_middleware_refactored.py +25 -2
  37. tests/test_proxy_only.py +84 -0
  38. tests/test_proxy_with_downloader.py +153 -0
  39. tests/test_real_scenario_proxy.py +17 -17
  40. tests/verify_mysql_warnings.py +110 -0
  41. crawlo/middleware/simple_proxy.py +0 -65
  42. {crawlo-1.4.5.dist-info → crawlo-1.4.6.dist-info}/WHEEL +0 -0
  43. {crawlo-1.4.5.dist-info → crawlo-1.4.6.dist-info}/entry_points.txt +0 -0
  44. {crawlo-1.4.5.dist-info → crawlo-1.4.6.dist-info}/top_level.txt +0 -0
@@ -64,6 +64,10 @@ MYSQL_DB = 'crawl_pro'
64
64
  MYSQL_TABLE = 'crawlo'
65
65
  MYSQL_BATCH_SIZE = 100
66
66
  MYSQL_USE_BATCH = False # 是否启用批量插入
67
+ # MySQL SQL生成行为控制配置
68
+ MYSQL_AUTO_UPDATE = False # 是否使用 REPLACE INTO(完全覆盖已存在记录)
69
+ MYSQL_INSERT_IGNORE = False # 是否使用 INSERT IGNORE(忽略重复数据)
70
+ MYSQL_UPDATE_COLUMNS = () # 冲突时需更新的列名;指定后 MYSQL_AUTO_UPDATE 失效
67
71
 
68
72
  # Redis配置
69
73
  redis_config = get_redis_config()
@@ -176,15 +180,39 @@ RANDOM_USER_AGENT_ENABLED = False # 是否启用随机用户代理
176
180
  # 站外过滤配置
177
181
  ALLOWED_DOMAINS = [] # 允许的域名列表
178
182
 
179
- # 代理配置
180
- PROXY_ENABLED = False # 是否启用代理
181
- PROXY_LIST = [] # 简化版代理配置(适用于SimpleProxyMiddleware)
182
- PROXY_API_URL = "" # 高级代理配置(适用于ProxyMiddleware)
183
- PROXY_EXTRACTOR = "proxy" # 代理提取方式
184
- PROXY_REFRESH_INTERVAL = 60 # 代理刷新间隔(秒)
185
- PROXY_API_TIMEOUT = 10 # 请求代理 API 超时时间
186
- PROXY_POOL_SIZE = 5 # 代理池大小
187
- PROXY_HEALTH_CHECK_THRESHOLD = 0.5 # 代理健康检查阈值
183
+ # 代理配置(通用版,支持静态代理列表和动态代理API两种模式)
184
+ PROXY_LIST = [] # 静态代理列表配置
185
+ PROXY_API_URL = "" # 动态代理API配置
186
+ # 代理提取配置,用于指定如何从API返回的数据中提取代理地址
187
+ # 可选值:
188
+ # - 字符串:直接作为字段名使用,如 "proxy"(默认值)
189
+ # - 字典:包含type和value字段,支持多种提取方式
190
+ # - {"type": "field", "value": "data"}:从指定字段提取
191
+ # - {"type": "jsonpath", "value": "$.data[0].proxy"}:使用JSONPath表达式提取
192
+ # - {"type": "custom", "function": your_function}:使用自定义函数提取
193
+ PROXY_EXTRACTOR = "proxy" # 代理提取配置
194
+ # 代理失败处理配置
195
+ PROXY_MAX_FAILED_ATTEMPTS = 3 # 代理最大失败尝试次数,超过此次数将标记为失效
196
+
197
+ # 代理使用示例:
198
+ # 1. 静态代理列表:
199
+ # PROXY_LIST = ["http://proxy1:8080", "http://proxy2:8080"]
200
+ # PROXY_API_URL = "" # 不使用动态代理
201
+ #
202
+ # 2. 动态代理API(默认字段提取):
203
+ # PROXY_LIST = [] # 不使用静态代理
204
+ # PROXY_API_URL = "http://api.example.com/get_proxy"
205
+ # PROXY_EXTRACTOR = "proxy" # 从"proxy"字段提取
206
+ #
207
+ # 3. 动态代理API(自定义字段提取):
208
+ # PROXY_LIST = [] # 不使用静态代理
209
+ # PROXY_API_URL = "http://api.example.com/get_proxy"
210
+ # PROXY_EXTRACTOR = "data" # 从"data"字段提取
211
+ #
212
+ # 4. 动态代理API(嵌套字段提取):
213
+ # PROXY_LIST = [] # 不使用静态代理
214
+ # PROXY_API_URL = "http://api.example.com/get_proxy"
215
+ # PROXY_EXTRACTOR = {"type": "field", "value": "result"} # 从"result"字段提取
188
216
 
189
217
  # 下载器通用配置
190
218
  DOWNLOAD_TIMEOUT = 30 # 下载超时时间(秒)
@@ -247,24 +275,4 @@ PLAYWRIGHT_MAX_PAGES_PER_BROWSER = 10 # 单浏览器最大页面数量
247
275
 
248
276
  # 通用优化配置
249
277
  CONNECTION_TTL_DNS_CACHE = 300 # DNS缓存TTL(秒)
250
- CONNECTION_KEEPALIVE_TIMEOUT = 15 # Keep-Alive超时(秒)
251
-
252
- # --------------------------------- 9. 数据存储配置 ------------------------------------
253
-
254
- # CSV管道配置
255
- CSV_DELIMITER = ',' # CSV分隔符
256
- CSV_QUOTECHAR = '"' # CSV引号字符
257
- CSV_INCLUDE_HEADERS = True # 是否包含表头
258
- CSV_EXTRASACTION = 'ignore' # 额外字段处理方式:ignore, raise
259
- CSV_FIELDNAMES = None # 字段名列表
260
- CSV_FILE = None # CSV文件路径
261
- CSV_DICT_FILE = None # CSV字典文件路径
262
- CSV_BATCH_SIZE = 100 # CSV批处理大小
263
- CSV_BATCH_FILE = None # CSV批处理文件路径
264
-
265
- # 数据库去重管道配置
266
- DB_HOST = 'localhost' # 数据库主机
267
- DB_PORT = 3306 # 数据库端口
268
- DB_USER = 'root' # 数据库用户
269
- DB_PASSWORD = '' # 数据库密码
270
- DB_NAME = 'crawlo' # 数据库名称
278
+ CONNECTION_KEEPALIVE = True # 是否启用HTTP连接保持
crawlo/stats_collector.py CHANGED
@@ -69,5 +69,14 @@ class StatsCollector(object):
69
69
  # 同时更新_stats中的spider_name
70
70
  self._stats['spider_name'] = spider_name
71
71
 
72
+ # 对统计信息中的浮点数进行四舍五入处理
73
+ formatted_stats = {}
74
+ for key, value in self._stats.items():
75
+ if isinstance(value, float):
76
+ # 对浮点数进行四舍五入,保留2位小数
77
+ formatted_stats[key] = round(value, 2)
78
+ else:
79
+ formatted_stats[key] = value
80
+
72
81
  # 输出统计信息(这是唯一输出统计信息的地方)
73
- self.logger.info(f'{spider_name} stats: \n{pformat(self._stats)}')
82
+ self.logger.info(f'{spider_name} stats: \n{pformat(formatted_stats)}')
@@ -87,6 +87,11 @@ MYSQL_TABLE = '{{project_name}}_data'
87
87
  MYSQL_BATCH_SIZE = 100
88
88
  MYSQL_USE_BATCH = False # 是否启用批量插入
89
89
 
90
+ # MySQL SQL生成行为控制配置
91
+ MYSQL_AUTO_UPDATE = False # 是否使用 REPLACE INTO(完全覆盖已存在记录)
92
+ MYSQL_INSERT_IGNORE = False # 是否使用 INSERT IGNORE(忽略重复数据)
93
+ MYSQL_UPDATE_COLUMNS = () # 冲突时需更新的列名;指定后 MYSQL_AUTO_UPDATE 失效
94
+
90
95
  # MongoDB配置
91
96
  MONGO_URI = 'mongodb://localhost:27017'
92
97
  MONGO_DATABASE = '{{project_name}}_db'
@@ -96,62 +101,12 @@ MONGO_MIN_POOL_SIZE = 20
96
101
  MONGO_BATCH_SIZE = 100 # 批量插入条数
97
102
  MONGO_USE_BATCH = False # 是否启用批量插入
98
103
 
99
- # =================================== 网络配置 ===================================
100
-
101
- # 代理配置
102
- # 代理功能默认不启用,如需使用请在项目配置文件中启用并配置相关参数
103
- PROXY_ENABLED = False # 是否启用代理
104
+ # =================================== 代理配置 ===================================
104
105
 
105
106
  # 简化版代理配置(适用于SimpleProxyMiddleware)
106
- PROXY_LIST = [] # 代理列表,例如: ["http://proxy1:8080", "http://proxy2:8080"]
107
+ # 只要配置了代理列表,中间件就会自动启用
108
+ # PROXY_LIST = ["http://proxy1:8080", "http://proxy2:8080"]
107
109
 
108
110
  # 高级代理配置(适用于ProxyMiddleware)
109
- PROXY_API_URL = "" # 代理获取接口(请替换为真实地址)
110
-
111
- # 代理提取方式(支持字段路径或函数)
112
- # 示例: "proxy" 适用于 {"proxy": "http://1.1.1.1:8080"}
113
- # 示例: "data.proxy" 适用于 {"data": {"proxy": "http://1.1.1.1:8080"}}
114
- PROXY_EXTRACTOR = "proxy"
115
-
116
- # 代理刷新控制
117
- PROXY_REFRESH_INTERVAL = 60 # 代理刷新间隔(秒)
118
- PROXY_API_TIMEOUT = 10 # 请求代理 API 超时时间
119
-
120
- # 浏览器指纹模拟(仅 CurlCffi 下载器有效)
121
- CURL_BROWSER_TYPE = "chrome" # 可选: chrome, edge, safari, firefox 或版本如 chrome136
122
-
123
- # 自定义浏览器版本映射(可覆盖默认行为)
124
- CURL_BROWSER_VERSION_MAP = {
125
- "chrome": "chrome136",
126
- "edge": "edge101",
127
- "safari": "safari184",
128
- "firefox": "firefox135",
129
- }
130
-
131
- # 下载器优化配置
132
- # 下载器健康检查
133
- DOWNLOADER_HEALTH_CHECK = True # 是否启用下载器健康检查
134
- HEALTH_CHECK_INTERVAL = 60 # 健康检查间隔(秒)
135
-
136
- # 请求统计配置
137
- REQUEST_STATS_ENABLED = True # 是否启用请求统计
138
- STATS_RESET_ON_START = False # 启动时是否重置统计
139
-
140
- # HttpX 下载器专用配置
141
- HTTPX_HTTP2 = True # 是否启用HTTP/2支持
142
- HTTPX_FOLLOW_REDIRECTS = True # 是否自动跟随重定向
143
-
144
- # AioHttp 下载器专用配置
145
- AIOHTTP_AUTO_DECOMPRESS = True # 是否自动解压响应
146
- AIOHTTP_FORCE_CLOSE = False # 是否强制关闭连接
147
-
148
- # 通用优化配置
149
- CONNECTION_TTL_DNS_CACHE = 300 # DNS缓存TTL(秒)
150
- CONNECTION_KEEPALIVE_TIMEOUT = 15 # Keep-Alive超时(秒)
151
-
152
- # 内存监控配置
153
- # 内存监控扩展默认不启用,如需使用请在项目配置文件中启用
154
- MEMORY_MONITOR_ENABLED = False # 是否启用内存监控
155
- MEMORY_MONITOR_INTERVAL = 60 # 内存监控检查间隔(秒)
156
- MEMORY_WARNING_THRESHOLD = 80.0 # 内存使用率警告阈值(百分比)
157
- MEMORY_CRITICAL_THRESHOLD = 90.0 # 内存使用率严重阈值(百分比)
111
+ # 只要配置了代理API URL,中间件就会自动启用
112
+ # PROXY_API_URL = "http://your-proxy-api.com/get-proxy"
@@ -92,6 +92,11 @@ MYSQL_TABLE = '{{project_name}}_data'
92
92
  MYSQL_BATCH_SIZE = 100
93
93
  MYSQL_USE_BATCH = True # 是否启用批量插入
94
94
 
95
+ # MySQL SQL生成行为控制配置
96
+ MYSQL_AUTO_UPDATE = False # 是否使用 REPLACE INTO(完全覆盖已存在记录)
97
+ MYSQL_INSERT_IGNORE = False # 是否使用 INSERT IGNORE(忽略重复数据)
98
+ MYSQL_UPDATE_COLUMNS = () # 冲突时需更新的列名;指定后 MYSQL_AUTO_UPDATE 失效
99
+
95
100
  # MongoDB配置
96
101
  MONGO_URI = 'mongodb://localhost:27017'
97
102
  MONGO_DATABASE = '{{project_name}}_db'
@@ -101,26 +106,7 @@ MONGO_MIN_POOL_SIZE = 20
101
106
  MONGO_BATCH_SIZE = 100 # 批量插入条数
102
107
  MONGO_USE_BATCH = True # 是否启用批量插入
103
108
 
104
- # =================================== 网络配置 ===================================
105
-
106
- # 代理配置
107
- # 代理功能默认不启用,如需使用请在项目配置文件中启用并配置相关参数
108
- PROXY_ENABLED = False # 是否启用代理
109
-
110
- # 简化版代理配置(适用于SimpleProxyMiddleware)
111
- PROXY_LIST = [] # 代理列表,例如: ["http://proxy1:8080", "http://proxy2:8080"]
112
-
113
- # 高级代理配置(适用于ProxyMiddleware)
114
- PROXY_API_URL = "" # 代理获取接口(请替换为真实地址)
115
-
116
- # 代理提取方式(支持字段路径或函数)
117
- # 示例: "proxy" 适用于 {"proxy": "http://1.1.1.1:8080"}
118
- # 示例: "data.proxy" 适用于 {"data": {"proxy": "http://1.1.1.1:8080"}}
119
- PROXY_EXTRACTOR = "proxy"
120
-
121
- # 代理刷新控制
122
- PROXY_REFRESH_INTERVAL = 60 # 代理刷新间隔(秒)
123
- PROXY_API_TIMEOUT = 10 # 请求代理 API 超时时间
109
+ # =================================== 浏览器指纹模拟 ===================================
124
110
 
125
111
  # 浏览器指纹模拟(仅 CurlCffi 下载器有效)
126
112
  CURL_BROWSER_TYPE = "chrome" # 可选: chrome, edge, safari, firefox 或版本如 chrome136
@@ -133,7 +119,8 @@ CURL_BROWSER_VERSION_MAP = {
133
119
  "firefox": "firefox135",
134
120
  }
135
121
 
136
- # 下载器优化配置
122
+ # =================================== 下载器优化配置 ===================================
123
+
137
124
  # 下载器健康检查
138
125
  DOWNLOADER_HEALTH_CHECK = True # 是否启用下载器健康检查
139
126
  HEALTH_CHECK_INTERVAL = 60 # 健康检查间隔(秒)
@@ -154,7 +141,18 @@ AIOHTTP_FORCE_CLOSE = False # 是否强制关闭连接
154
141
  CONNECTION_TTL_DNS_CACHE = 300 # DNS缓存TTL(秒)
155
142
  CONNECTION_KEEPALIVE_TIMEOUT = 15 # Keep-Alive超时(秒)
156
143
 
157
- # 内存监控配置
144
+ # =================================== 代理配置 ===================================
145
+
146
+ # 简化版代理配置(适用于SimpleProxyMiddleware)
147
+ # 只要配置了代理列表,中间件就会自动启用
148
+ # PROXY_LIST = ["http://proxy1:8080", "http://proxy2:8080"]
149
+
150
+ # 高级代理配置(适用于ProxyMiddleware)
151
+ # 只要配置了代理API URL,中间件就会自动启用
152
+ # PROXY_API_URL = "http://your-proxy-api.com/get-proxy"
153
+
154
+ # =================================== 内存监控配置 ===================================
155
+
158
156
  # 内存监控扩展默认不启用,如需使用请在项目配置文件中启用
159
157
  MEMORY_MONITOR_ENABLED = False # 是否启用内存监控
160
158
  MEMORY_MONITOR_INTERVAL = 60 # 内存监控检查间隔(秒)
@@ -102,6 +102,11 @@ MYSQL_TABLE = '{{project_name}}_data'
102
102
  MYSQL_BATCH_SIZE = 100
103
103
  MYSQL_USE_BATCH = False # 是否启用批量插入
104
104
 
105
+ # MySQL SQL生成行为控制配置
106
+ MYSQL_AUTO_UPDATE = False # 是否使用 REPLACE INTO(完全覆盖已存在记录)
107
+ MYSQL_INSERT_IGNORE = False # 是否使用 INSERT IGNORE(忽略重复数据)
108
+ MYSQL_UPDATE_COLUMNS = () # 冲突时需更新的列名;指定后 MYSQL_AUTO_UPDATE 失效
109
+
105
110
  # MongoDB配置
106
111
  MONGO_URI = 'mongodb://localhost:27017'
107
112
  MONGO_DATABASE = '{{project_name}}_db'
@@ -103,6 +103,11 @@ MYSQL_TABLE = '{{project_name}}_data'
103
103
  MYSQL_BATCH_SIZE = 100
104
104
  MYSQL_USE_BATCH = True # 是否启用批量插入
105
105
 
106
+ # MySQL SQL生成行为控制配置
107
+ MYSQL_AUTO_UPDATE = False # 是否使用 REPLACE INTO(完全覆盖已存在记录)
108
+ MYSQL_INSERT_IGNORE = False # 是否使用 INSERT IGNORE(忽略重复数据)
109
+ MYSQL_UPDATE_COLUMNS = () # 冲突时需更新的列名;指定后 MYSQL_AUTO_UPDATE 失效
110
+
106
111
  # MongoDB配置
107
112
  MONGO_URI = 'mongodb://localhost:27017'
108
113
  MONGO_DATABASE = '{{project_name}}_db'
@@ -74,4 +74,28 @@ LOG_ENCODING = 'utf-8' # 明确指定日志文件编码
74
74
  STATS_DUMP = True
75
75
 
76
76
  # 输出配置
77
- OUTPUT_DIR = 'output'
77
+ OUTPUT_DIR = 'output'
78
+
79
+ # =================================== 数据库配置 ===================================
80
+
81
+ # MySQL配置
82
+ MYSQL_HOST = '127.0.0.1'
83
+ MYSQL_PORT = 3306
84
+ MYSQL_USER = 'root'
85
+ MYSQL_PASSWORD = '123456'
86
+ MYSQL_DB = '{{project_name}}'
87
+ MYSQL_TABLE = '{{project_name}}_data'
88
+ MYSQL_BATCH_SIZE = 100
89
+ MYSQL_USE_BATCH = False # 是否启用批量插入
90
+
91
+ # MySQL SQL生成行为控制配置
92
+ MYSQL_AUTO_UPDATE = False # 是否使用 REPLACE INTO(完全覆盖已存在记录)
93
+ MYSQL_INSERT_IGNORE = False # 是否使用 INSERT IGNORE(忽略重复数据)
94
+ MYSQL_UPDATE_COLUMNS = () # 冲突时需更新的列名;指定后 MYSQL_AUTO_UPDATE 失效
95
+
96
+ # MongoDB配置
97
+ MONGO_URI = 'mongodb://localhost:27017'
98
+ MONGO_DATABASE = '{{project_name}}_db'
99
+ MONGO_COLLECTION = '{{project_name}}_items'
100
+ MONGO_BATCH_SIZE = 100 # 批量插入条数
101
+ MONGO_USE_BATCH = False # 是否启用批量插入
@@ -100,6 +100,11 @@ MYSQL_TABLE = '{{project_name}}_data'
100
100
  MYSQL_BATCH_SIZE = 100
101
101
  MYSQL_USE_BATCH = False # 是否启用批量插入
102
102
 
103
+ # MySQL SQL生成行为控制配置
104
+ MYSQL_AUTO_UPDATE = False # 是否使用 REPLACE INTO(完全覆盖已存在记录)
105
+ MYSQL_INSERT_IGNORE = False # 是否使用 INSERT IGNORE(忽略重复数据)
106
+ MYSQL_UPDATE_COLUMNS = () # 冲突时需更新的列名;指定后 MYSQL_AUTO_UPDATE 失效
107
+
103
108
  # MongoDB配置
104
109
  MONGO_URI = 'mongodb://localhost:27017'
105
110
  MONGO_DATABASE = '{{project_name}}_db'
@@ -1,13 +1,6 @@
1
- #!/usr/bin/env python3
1
+ #!/usr/bin/python
2
2
  # -*- coding: UTF-8 -*-
3
- """
4
- {{project_name}} 项目运行脚本
5
- ============================
6
- 基于 Crawlo 框架的简化爬虫启动器。
7
3
 
8
- 框架会自动处理爬虫模块的导入和注册,用户无需手动导入。
9
- 框架会自动从settings.py中读取SPIDER_MODULES配置。
10
- """
11
4
  import sys
12
5
  import asyncio
13
6
 
@@ -3,142 +3,39 @@
3
3
  {{project_name}}.spiders.{{spider_name}}
4
4
  =======================================
5
5
  由 `crawlo genspider` 命令生成的爬虫。
6
- 基于 Crawlo 框架,支持异步并发、分布式爬取等功能。
7
-
8
- 使用示例:
9
- crawlo crawl {{spider_name}}
10
6
  """
11
7
 
12
8
  from crawlo.spider import Spider
13
9
  from crawlo import Request
14
- from ..items import ExampleItem
10
+ from ..items import {{item_class}}
15
11
 
16
12
 
17
13
  class {{class_name}}(Spider):
18
14
  """
19
15
  爬虫:{{spider_name}}
20
-
21
- 功能说明:
22
- - 支持并发爬取
23
- - 自动去重过滤
24
- - 错误重试机制
25
- - 数据管道处理
26
16
  """
27
17
  name = '{{spider_name}}'
28
18
  allowed_domains = ['{{domain}}']
29
19
  start_urls = ['https://{{domain}}/']
30
20
 
31
- # 高级配置(可选)
32
- # custom_settings = {
33
- # 'DOWNLOAD_DELAY': 2.0,
34
- # 'CONCURRENCY': 4,
35
- # 'RETRY_HTTP_CODES': [500, 502, 503, 504, 408, 429],
36
- # 'ALLOWED_RESPONSE_CODES': [200, 301, 302], # 只允许特定状态码
37
- # 'DENIED_RESPONSE_CODES': [403, 404], # 拒绝特定状态码
38
- # }
21
+ # 自定义设置(可选)
22
+ custom_settings = {}
39
23
 
40
24
  def start_requests(self):
41
25
  """
42
26
  生成初始请求。
43
-
44
- 支持自定义请求头、代理、优先级等。
45
27
  """
46
- headers = {
47
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
48
- 'Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
49
- }
50
-
51
28
  for url in self.start_urls:
52
- yield Request(
53
- url=url,
54
- callback=self.parse,
55
- headers=headers,
56
- # meta={'proxy': 'http://proxy.example.com:8080'}, # 自定义代理
57
- # priority=10, # 请求优先级(数字越大优先级越高)
58
- )
29
+ yield Request(url=url, callback=self.parse)
59
30
 
60
31
  def parse(self, response):
61
32
  """
62
33
  解析响应的主方法。
63
-
64
- Args:
65
- response: 响应对象,包含页面内容和元数据
66
-
67
- Yields:
68
- Request: 新的请求对象(用于深度爬取)
69
- Item: 数据项对象(用于数据存储)
70
34
  """
71
35
  self.logger.info(f'正在解析页面: {response.url}')
72
36
 
73
- # ================== 数据提取示例 ==================
74
-
75
- # 提取数据并创建 Item
76
- # item = {{item_class}}()
77
- # item['title'] = response.xpath('//title/text()').get(default='')
78
- # item['url'] = response.url
79
- # item['content'] = response.xpath('//div[@class="content"]//text()').getall()
80
- # yield item
81
-
82
- # 直接返回字典(简单数据)
83
37
  yield {
84
38
  'title': response.xpath('//title/text()').get(default=''),
85
39
  'url': response.url,
86
40
  'status_code': response.status_code,
87
- # 'description': response.xpath('//meta[@name="description"]/@content').get(),
88
- # 'keywords': response.xpath('//meta[@name="keywords"]/@content').get(),
89
- }
90
-
91
- # ================== 链接提取示例 ==================
92
-
93
- # 提取并跟进链接
94
- # links = response.xpath('//a/@href').getall()
95
- # for link in links:
96
- # # 过滤有效链接
97
- # if link and not link.startswith(('javascript:', 'mailto:', '#')):
98
- # yield response.follow(
99
- # link,
100
- # callback=self.parse_detail, # 或者 self.parse 继续递归
101
- # meta={'parent_url': response.url} # 传递父页面信息
102
- # )
103
-
104
- # 用 CSS 选择器提取链接
105
- # for link in response.css('a.item-link::attr(href)').getall():
106
- # yield response.follow(link, callback=self.parse_detail)
107
-
108
- # ================== 分页处理示例 ==================
109
-
110
- # 处理分页
111
- # next_page = response.xpath('//a[@class="next"]/@href').get()
112
- # if next_page:
113
- # yield response.follow(next_page, callback=self.parse)
114
-
115
- # 数字分页
116
- # current_page = int(response.meta.get('page', 1))
117
- # max_pages = 100 # 设置最大页数
118
- # if current_page < max_pages:
119
- # next_url = f'https://{{domain}}/page/{current_page + 1}'
120
- # yield Request(
121
- # url=next_url,
122
- # callback=self.parse,
123
- # meta={'page': current_page + 1}
124
- # )
125
-
126
- def parse_detail(self, response):
127
- """
128
- 解析详情页面的方法(可选)。
129
-
130
- 用于处理从列表页跳转而来的详情页。
131
- """
132
- self.logger.info(f'正在解析详情页: {response.url}')
133
-
134
- # parent_url = response.meta.get('parent_url', '')
135
- #
136
- # yield {
137
- # 'title': response.xpath('//h1/text()').get(default=''),
138
- # 'content': '\n'.join(response.xpath('//div[@class="content"]//text()').getall()),
139
- # 'url': response.url,
140
- # 'parent_url': parent_url,
141
- # 'publish_time': response.xpath('//time/@datetime').get(),
142
- # }
143
-
144
- pass
41
+ }
crawlo/utils/db_helper.py CHANGED
@@ -93,7 +93,7 @@ class SQLBuilder:
93
93
  @staticmethod
94
94
  def _build_update_clause(update_columns: Union[Tuple, List]) -> str:
95
95
  """
96
- 构建更新子句
96
+ 构建更新子句,使用新的 MySQL 语法避免 VALUES() 函数弃用警告
97
97
 
98
98
  Args:
99
99
  update_columns (tuple or list): 更新列名
@@ -103,7 +103,9 @@ class SQLBuilder:
103
103
  """
104
104
  if not isinstance(update_columns, (tuple, list)):
105
105
  update_columns = (update_columns,)
106
- return ", ".join(f"`{key}`=VALUES(`{key}`)" for key in update_columns)
106
+ # 使用新的语法:INSERT ... VALUES (...) AS alias ... UPDATE ... alias.col
107
+ # 确保使用 excluded 别名而不是 VALUES() 函数
108
+ return ", ".join(f"`{key}`=`excluded`.`{key}`" for key in update_columns)
107
109
 
108
110
  @staticmethod
109
111
  def make_insert(
@@ -133,7 +135,8 @@ class SQLBuilder:
133
135
  if update_columns:
134
136
  update_clause = SQLBuilder._build_update_clause(update_columns)
135
137
  ignore_flag = " IGNORE" if insert_ignore else ""
136
- sql = f"INSERT{ignore_flag} INTO `{table}` {keys_str} VALUES {values_str} ON DUPLICATE KEY UPDATE {update_clause}"
138
+ # 使用新的语法避免 VALUES() 函数弃用警告
139
+ sql = f"INSERT{ignore_flag} INTO `{table}` {keys_str} VALUES {values_str} AS `excluded` ON DUPLICATE KEY UPDATE {update_clause}"
137
140
 
138
141
  elif auto_update:
139
142
  sql = f"REPLACE INTO `{table}` {keys_str} VALUES {values_str}"
@@ -225,16 +228,19 @@ class SQLBuilder:
225
228
  update_columns = (update_columns,)
226
229
 
227
230
  if update_columns_value:
231
+ # 当提供了固定值时,使用这些值进行更新
228
232
  update_pairs = [
229
233
  f"`{key}`={value}"
230
234
  for key, value in zip(update_columns, update_columns_value)
231
235
  ]
232
236
  else:
237
+ # 使用新的语法避免 VALUES() 函数弃用警告
238
+ # INSERT ... VALUES (...) AS excluded ... ON DUPLICATE KEY UPDATE col=excluded.col
233
239
  update_pairs = [
234
- f"`{key}`=VALUES(`{key}`)" for key in update_columns
240
+ f"`{key}`=`excluded`.`{key}`" for key in update_columns
235
241
  ]
236
242
  update_clause = ", ".join(update_pairs)
237
- sql = f"INSERT INTO `{table}` ({keys_str}) VALUES ({placeholders_str}) ON DUPLICATE KEY UPDATE {update_clause}"
243
+ sql = f"INSERT INTO `{table}` ({keys_str}) VALUES ({placeholders_str}) AS `excluded` ON DUPLICATE KEY UPDATE {update_clause}"
238
244
 
239
245
  elif auto_update:
240
246
  sql = f"REPLACE INTO `{table}` ({keys_str}) VALUES ({placeholders_str})"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: crawlo
3
- Version: 1.4.5
3
+ Version: 1.4.6
4
4
  Summary: Crawlo 是一款基于异步IO的高性能Python爬虫框架,支持分布式抓取。
5
5
  Home-page: https://github.com/crawl-coder/Crawlo.git
6
6
  Author: crawl-coder