上个月接了个舆情监控的私活,需要从几个新闻网站抓取指定关键词的文章,数据量不大但要求实时。我第一反应是写个Scrapy脚本,但转念一想,这种活以后肯定还会遇到,干脆用AI辅助搭一套可复用的分布式爬虫。今天把这过程记录下来,有代码,有坑,有优化。

场景和目标

目标明确:抓取新浪新闻、腾讯新闻和36氪的搜索页,关键词是「AI芯片」,每15分钟跑一轮,去重后存MySQL。单机跑也行,但为了练手,我决定用Scrapy+Redis做分布式去重,用ChatGPT生成大部分代码。

技术栈:Python 3.10 + Scrapy 2.11 + Redis 7.0 + MySQL 8.0 + Pandas + GPT-4(辅助清洗)。环境是Win11,代码用VS Code。

环境准备

# 创建虚拟环境 python -m venv venv venv\Scripts\activate # Windows # 安装依赖 pip install scrapy scrapy-redis pymysql pandas openai # 启动Redis(Windows用WSL或Docker) docker run -d --name redis -p 6379:6379 redis:7

安装搞定。接下来是重头戏——怎么让ChatGPT产出能跑的Scrapy代码。

用ChatGPT生成爬虫框架

我一开始的提示词很随意:「写一个Scrapy爬虫抓取新闻」。结果代码跑起来全是问题。后来我总结了三段式提示词:背景、需求、输出格式。

# 提示词模板 """ 背景:我要抓取百度新闻搜索页,关键词是AI芯片,需要提取标题、链接、来源、时间。 需求:用Scrapy实现,遵守robots协议,设置下载延迟,处理分页,输出json文件。 输出:给出完整项目结构和每个文件的关键代码。 """

这提示词生成的代码基本能用,但有个问题:百度新闻的HTML结构变了,CSS选择器失效。我加了一句「如果找不到元素,请用xpath替代」,然后GPT-4给出了xpath版本。这里有个技巧:让AI给出两种选择器方案,并说明各自适用场景

分布式改造:Scrapy-Redis + 去重

单机爬虫改成分布式,核心是换调度器。我让ChatGPT生成配置,然后手动调优。关键配置如下:

# settings.py 关键修改 SCHEDULER = "scrapy_redis.scheduler.Scheduler" DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" REDIS_URL = "redis://localhost:6379" # 持久化队列,爬虫重启不丢请求 SCHEDULER_PERSIST = True

这里有个坑:默认的RFPDupeFilter是对URL的指纹去重,但新闻网站经常有相同内容不同URL(比如带utm参数)。我让AI优化去重逻辑,结果它建议用自定义指纹函数,把URL规范化后再取指纹。我照着改:

from scrapy.utils.request import request_fingerprint import hashlib def request_fingerprint(request): # 规范化URL:去掉参数中的utm_*和spm url = request.url.split('?')[0] query = request.url.split('?')[1] if '?' in request.url else '' params = [p for p in query.split('&') if not p.startswith(('utm_', 'spm'))] clean_url = url + '?' + '&'.join(params) if params else url return hashlib.md5(clean_url.encode()).hexdigest()

改完指纹函数,去重效果立竿见影。抓了1000条数据,重复率从15%降到3%。

核心洞见:AI能帮你生成80%的代码,但剩下20%的调优,比如去重指纹、选择器容错,必须靠你对业务的理解去驱动。

数据清洗:Pandas + AI辅助

抓下来的数据脏得很:标题里有特殊符号、时间格式不统一、来源字段有的空。我本来想全用规则清洗,但写正则太烦。试了下让GPT-4写清洗函数,效果不错。

import pandas as pd def clean_data(df): # 去重 df.drop_duplicates(subset=['title'], keep='first', inplace=True) # 清洗标题:去掉特殊字符 df['title'] = df['title'].str.replace(r'[\n\r\t]', '', regex=True) # 统一时间格式 df['pub_time'] = pd.to_datetime(df['pub_time'], errors='coerce') # 填充缺失的source df['source'] = df['source'].fillna('未知') return df

但有些脏数据规则搞不定,比如「AI芯片」和「AI 芯片」这种同义词。我让ChatGPT写了个用GPT-4 API做语义去重的脚本:

import openai def is_duplicate(text1, text2): prompt = f"判断两篇文章是否语义相同:\n1: {text1}\n2: {text2}\n只回答是或否" resp = openai.ChatCompletion.create( model="gpt-4", messages=[{"role":"user","content":prompt}], max_tokens=5 ) return resp.choices[0].message.content.strip() == "是"

这个API调用太贵,所以只对标题相似度>0.8的pair做检查。成本控制在每天50 cents,换来了更准的去重。

常见问题与坑

  • 403反爬:AI生成的默认UA是Scrapy/2.11,被百度识别封IP。让ChatGPT生成随机UA中间件,问题解决。
  • Redis连接池耗尽:爬虫并发高时报错。调大连接池,并设置超时重试。
  • 数据入库乱码:MySQL连接字符串要加charset=utf8mb4,AI生成的代码没带,导致emoji变问号。
  • GPT-4 API超时:清洗时调用频繁会超时,加retry和 delay。

总结

整个项目从零到跑通用了3天,其中ChatGPT贡献了约60%的代码,剩下的是调试和优化。AI最大的价值不是省写代码的时间,而是减少查文档的时间——我遇到不熟悉的中间件配置,直接问AI,秒回。

但AI不是万能的,它不懂你的业务上下文。比如去重策略,必须你自己定方案,AI只是执行。工具永远是工具,关键还是你的判断力。

如果有类似的数据抓取或清洗需求,欢迎来铭锦数智聊聊,我们也在用AI做更多自动化的事。