ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Scrapy爬取校园集市:动态渲染与增量抓取实战

Scrapy爬取校园集市:动态渲染与增量抓取实战 简介本资源是一套基于Python Scrapy框架开发的校园集市数据采集系统源码面向Python初学者、爬虫实践者及高校信息分析相关课程学习者旨在解决校园社区平台结构化数据自动化获取难题支撑后续信息整理、热度趋势建模与情感分析等研究场景。压缩包共48个文件含24个.py脚本覆盖爬虫主逻辑、中间件、Pipeline、数据库连接、热词计算、BERT/Word2Vec相关性分析等核心模块、11个.txt说明文档含环境配置、使用指引与注意事项、2个.sql数据库建表脚本、以及cfg、gitignore、LICENSE等工程必需文件整体仅120KB轻量易部署。已有51人下载学习资源目录结构规范按spiders、pipelines、utils、config等分层组织内含完整可运行流程从start.py启动到mysql_struct.sql建库并提供多版本依赖文件requirements.txt/.zbak与备份机制便于调试对比与环境复现。1. 项目概述为什么校园集市数据值得用Scrapy专门爬取“赞噢校园集市”这个名称一听就带着鲜明的场景特征——它不是泛泛而谈的电商大平台而是聚焦高校学生群体、主打二手教材、闲置数码、考研资料、校园周边服务的垂直型本地化交易社区。我去年帮三所高校的创业社团做过数据支持发现这类平台有四个典型特点首页轮播图频繁更新但结构稳定商品列表页采用分页懒加载混合模式详情页嵌套多个iframe展示卖家信用、物流状态、评价聚合最关键的是用户行为数据如收藏数、浏览量、发布时间几乎全部通过AJAX异步加载且接口路径藏在JS变量里不抓包根本找不到真实请求地址。这恰恰是Scrapy最擅长啃的硬骨头——它不像RequestsBeautifulSoup那样需要手动拼接URL、管理Session、解析JS上下文而是通过Spider中间件Downloader MiddlewareItem Pipeline三层解耦架构把“识别页面结构→提取URL规则→构造请求→解析响应→清洗存储”整个链条模块化。比如我们遇到的iframe问题Scrapy本身不执行JS但配合Splash或Playwright中间件就能自然接管渲染流程比硬写Selenium脚本少掉80%的维护成本。再比如反爬策略校园集市这类平台通常不会上Cloudflare人机验证但会校验Referer、User-Agent、Cookie过期时间Scrapy的ROTATING_PROXY和RANDOM_USER_AGENT扩展包能自动轮换连IP池都不用自己搭。所以这个项目标题里的“基于Python Scrapy框架”不是为了凑技术名词而是因为Scrapy在校园场景下具备不可替代的工程优势它能把一个需要人工点开20个页面才能确认的“教材价格波动规律”压缩成一条命令scrapy crawl textbook_trend -a start_date2024-03-01就跑完全量数据。如果你正在做校园消费行为分析、二手书定价模型、或者想给学生开发比价插件这套源码就是现成的弹药库。2. 整体架构设计与核心思路拆解2.1 为什么放弃RequestsBeautifulSoup而选择Scrapy很多人看到“爬虫”第一反应就是Requests但我在实际项目中踩过太多坑。去年给某高校图书馆做的旧书回收系统初期用Requests写了500行脚本结果遇到三个致命问题第一当网站把商品列表从/list?page1改成/api/v2/items?offset0limit20时所有正则匹配全部失效第二每次新增一个字段比如加个“是否支持面交”标签都要手动改XPath并测试20个页面第三当需要同时抓取商品页、卖家主页、评价详情页时回调函数嵌套四层调试起来像在迷宫里找出口。Scrapy的解决方案是把“变”和“不变”彻底分离Spider只负责定义“去哪里抓”Item定义“抓什么”Pipeline负责“怎么存”。比如针对赞噢校园集市我们定义ZanOhItem类时字段名直接对应数据库表结构class ZanOhItem(scrapy.Item): item_id scrapy.Field() # 商品唯一ID用于去重 title scrapy.Field() # 标题清洗掉【急售】【学长转卖】等前缀 price scrapy.Field() # 价格字符串转float处理“面议”“私聊”等异常值 original_price scrapy.Field() # 原价用于计算折扣率 seller_id scrapy.Field() # 卖家ID关联用户画像 view_count scrapy.Field() # 浏览量需从JS变量中提取 collect_count scrapy.Field() # 收藏数同上 publish_time scrapy.Field() # 发布时间转换为标准datetime格式 campus scrapy.Field() # 所属校区从URL路径或页面meta标签提取这样做的好处是当网站把“浏览量”字段从span classviews128/span改成div>rules ( Rule(LinkExtractor(allowr/list/campus/\d/page/\d), followTrue), Rule(LinkExtractor(allowr/item/\d), callbackparse_item), )Scrapy会自动遍历所有符合规则的链接根本不用手写循环翻页逻辑。这种设计思想本质上是把爬虫从“过程式编程”升级为“声明式编程”让开发者专注业务逻辑而非底层调度。2.2 动态内容处理Scrapy Playwright的协同机制赞噢校园集市的详情页里浏览量、收藏数、卖家信用分这些关键数据都藏在iframe里而iframe的src是动态生成的。比如主页面有个iframe src/widget/stats?id123456/iframe但这个id参数其实是JS运行时从window.__INITIAL_STATE__对象里读出来的。Requests无法执行JSSelenium又太重这时候Playwright就成为最优解。我们的方案不是用Playwright替代Scrapy而是让它作为Scrapy的“眼睛”——在Downloader Middleware里拦截请求对需要渲染的URL调用Playwright获取HTML再把结果交给Scrapy的标准解析流程。具体实现分三步识别需要渲染的页面在Spider的start_requests方法里给目标URL打上playwrightTrue标记Middleware接管请求自定义PlaywrightMiddleware检测到playwrightTrue就启动Playwright实例等待#stats-container元素出现后再截图无缝传递响应把Playwright返回的HTML字符串包装成HtmlResponse对象Scrapy后续流程完全无感。这样做的好处是90%的静态页面走Scrapy原生流程毫秒级响应只有10%的动态页面才启动Playwright耗时约1.2秒整体效率比全程用Playwright高3倍。实测下来抓取1000个商品详情页纯Scrapy方案要12分钟而混合方案只要4分30秒——因为Playwright只在必要时才启动且可以复用浏览器上下文。2.3 数据去重与增量抓取Redis作为中央调度器校园集市的数据更新频率很高教材类商品可能一天内价格变动3次但全量抓取既浪费带宽又增加服务器压力。我们的解决方案是用Redis做两件事一是URL指纹去重二是增量时间戳记录。Scrapy-Redis扩展包已经封装了RFPDupeFilter类但默认只对URL去重而赞噢校园集市存在“同一商品不同校区显示不同价格”的情况所以我们要把campus_id也纳入指纹计算。修改settings.pyDUPEFILTER_CLASS scrapy_redis.dupefilter.RFPDupeFilter SCHEDULER scrapy_redis.scheduler.Scheduler SCHEDULER_PERSIST True REDIS_URL redis://localhost:6379 # 自定义指纹生成逻辑 def request_fingerprint(request): from scrapy.utils.request import request_fingerprint # 基础指纹 校区参数 base_fp request_fingerprint(request) campus request.meta.get(campus_id, all) return f{base_fp}_{campus}更关键的是增量抓取。我们在Redis里维护一个last_crawl_time键每次抓取前先读取这个时间戳然后只请求publish_time last_crawl_time的商品。Spider启动时自动更新这个键def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.redis_client redis.Redis.from_url(settings.REDIS_URL) self.last_time self.redis_client.get(last_crawl_time) or b2024-01-01 self.redis_client.set(last_crawl_time, datetime.now().strftime(%Y-%m-%d %H:%M:%S))这样既能保证数据新鲜度又避免重复抓取历史数据。实测表明在日均新增2000条商品的校园集市上增量抓取使带宽消耗降低76%CPU占用下降42%。3. 核心细节解析与实操要点3.1 Spider编写从页面结构到代码映射的完整链路以抓取“教材类商品”为例我们先人工分析页面结构。打开https://zanoh.edu.cn/list/campus/101/page/1假设101是北大校区ID发现商品列表是标准的div classitem-card容器每个卡片包含标题、价格、发布时间。但注意两个陷阱第一价格显示为“¥35.00”或“面议”需要统一处理第二发布时间是“2小时前”“昨天”这类相对时间必须转换为绝对时间戳。Scrapy的CSS选择器能精准定位def parse_list(self, response): for card in response.css(div.item-card): item ZanOhItem() item[item_id] card.css(::attr(data-id)).get() item[title] card.css(h3.title::text).get().strip() # 价格清洗移除¥符号处理“面议” price_text card.css(span.price::text).get() if price_text and 面议 not in price_text: item[price] float(re.sub(r[^\d.], , price_text)) else: item[price] None # 时间转换将“2小时前”转为datetime time_text card.css(span.time::text).get() item[publish_time] self.parse_relative_time(time_text) # 构造详情页URL注意这里用绝对URL避免相对路径错误 detail_url response.urljoin(card.css(a::attr(href)).get()) yield scrapy.Request( urldetail_url, callbackself.parse_item, meta{item: item} # 把基础字段传给详情页解析 )这里的关键技巧是response.urljoin()——很多新手直接拼接字符串结果遇到/item/123这种相对路径就404。而urljoin会自动补全协议和域名鲁棒性极强。另一个重点是meta参数它像快递单号一样把初步提取的数据带到下一个回调函数避免在详情页重新解析标题和价格节省30%的CPU时间。3.2 动态字段提取从JS变量中“抠”出隐藏数据详情页的浏览量和收藏数藏在script标签里类似这样script window.__INITIAL_STATE__ { stats: { viewCount: 156, collectCount: 23, sellerCredit: 98.7 } }; /scriptScrapy原生不解析JS但我们用正则表达式精准捕获def parse_item(self, response): item response.meta[item] # 提取JS变量中的统计信息 script_content response.css(script:contains(__INITIAL_STATE__)::text).get() if script_content: # 匹配JSON片段 match re.search(rwindow\.__INITIAL_STATE__\s*\s*({.*?});, script_content, re.DOTALL) if match: try: stats_data json.loads(match.group(1)) item[view_count] stats_data.get(stats, {}).get(viewCount, 0) item[collect_count] stats_data.get(stats, {}).get(collectCount, 0) item[seller_credit] stats_data.get(stats, {}).get(sellerCredit, 0) except json.JSONDecodeError: self.logger.warning(fFailed to parse stats JSON for {response.url}) # 其他静态字段继续用CSS提取 item[original_price] response.css(span.original-price::text).get() item[campus] response.css(meta[namecampus]::attr(content)).get() yield item这个方案比用Playwright渲染整个页面快10倍因为正则匹配毫秒级完成。但要注意re.DOTALL标志否则多行JS代码里的换行符会让正则失败。另外try-except必不可少——线上环境总有JS格式意外变化不能让单个页面错误导致整个爬虫崩溃。3.3 Item Pipeline数据清洗不只是存数据库Pipeline是Scrapy的“质检车间”我们在这里做三件事类型转换、业务校验、数据增强。以价格字段为例原始数据可能是字符串“¥35.00”或数字35Pipeline统一转为floatclass DataCleaningPipeline: def process_item(self, item, spider): # 价格标准化 if item.get(price) and isinstance(item[price], str): item[price] float(re.sub(r[^\d.], , item[price])) # 时间标准化 if item.get(publish_time) and isinstance(item[publish_time], str): # 处理ISO格式和中文格式 if 年 in item[publish_time]: item[publish_time] datetime.strptime(item[publish_time], %Y年%m月%d日 %H:%M) else: item[publish_time] datetime.fromisoformat(item[publish_time].replace(Z, 00:00)) # 业务校验教材价格不能低于1元或高于500元 if item.get(price) and not (1 item[price] 500): spider.logger.warning(fInvalid price {item[price]} for {item.get(title)}) item[price] None # 数据增强计算折扣率如果有原价 if item.get(price) and item.get(original_price): try: item[discount_rate] round((item[original_price] - item[price]) / item[original_price], 2) except (ZeroDivisionError, TypeError): item[discount_rate] 0 return item这里有个重要经验Pipeline必须返回item否则数据流中断。很多新手忘记return item结果数据消失得莫名其妙。另外spider.logger.warning比print()更专业日志会自动带上爬虫名称和时间戳方便排查问题。3.4 存储方案选型MySQL vs MongoDB的实战权衡校园集市数据有强关系特征商品属于某个校区卖家有多个商品评价关联商品和用户。最初我们用MongoDB觉得JSON存储灵活但很快遇到三个问题第一查“北大校区所有价格低于30元的教材”需要聚合管道响应慢第二卖家信用分要实时计算MongoDB的原子操作不如MySQL可靠第三导出数据给BI工具时MongoDB的BSON格式兼容性差。最终切换到MySQL并设计了三张核心表-- 商品主表 CREATE TABLE zanoh_items ( id BIGINT PRIMARY KEY AUTO_INCREMENT, item_id VARCHAR(32) UNIQUE NOT NULL, title VARCHAR(255) NOT NULL, price DECIMAL(10,2), original_price DECIMAL(10,2), view_count INT DEFAULT 0, collect_count INT DEFAULT 0, publish_time DATETIME NOT NULL, campus VARCHAR(50) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 卖家表独立存储避免商品表冗余 CREATE TABLE zanoh_sellers ( id BIGINT PRIMARY KEY AUTO_INCREMENT, seller_id VARCHAR(32) UNIQUE NOT NULL, credit_score DECIMAL(5,2) DEFAULT 0, join_date DATE ); -- 关联表商品-卖家 CREATE TABLE item_seller_link ( item_id VARCHAR(32) NOT NULL, seller_id VARCHAR(32) NOT NULL, FOREIGN KEY (item_id) REFERENCES zanoh_items(item_id), FOREIGN KEY (seller_id) REFERENCES zanoh_sellers(seller_id) );Scrapy的SQLAlchemyPipeline能自动映射Item字段到表结构但要注意item_id作为业务主键必须在MySQL里设为UNIQUE否则重复插入会报错。我们还在Pipeline里加了ON DUPLICATE KEY UPDATE逻辑def process_item(self, item, spider): stmt insert(ZanOhItemModel).values(**dict(item)) stmt stmt.on_conflict_do_update( index_elements[item_id], set_dict( titleitem[title], priceitem[price], view_countitem[view_count], updated_atdatetime.now() ) ) self.session.execute(stmt) self.session.commit() return item这样即使网络抖动导致重复提交数据也不会错乱。实测表明MySQL方案使复杂查询速度提升5倍且支持标准SQL导出教研团队直接用Excel连接就能分析。4. 实操过程与核心环节实现4.1 环境搭建避开Python版本和依赖冲突的深坑Scrapy官方推荐Python 3.8但校园集市的JS渲染依赖Playwright而Playwright 1.32要求Python 3.9。我们实测发现用Python 3.11.5是最稳妥的选择——它兼容所有主流Scrapy扩展且Playwright的Chromium二进制包下载成功率100%。安装步骤必须严格按顺序# 1. 创建隔离环境绝对不要用系统Python python -m venv scrapy_env source scrapy_env/bin/activate # Linux/Mac # scrapy_env\Scripts\activate # Windows # 2. 升级pip避免旧版pip安装wheel失败 pip install --upgrade pip # 3. 安装核心依赖注意scrapy-playwright必须在scrapy之前 pip install scrapy-playwright pip install scrapy redis playwright # 4. 下载Playwright浏览器关键很多新手卡在这步 playwright install chromium # 5. 验证安装运行这个命令应该输出Scrapy版本 scrapy version最大的坑在于playwright install。如果网络不稳定它会卡在下载Chromium此时不要CtrlC而是用playwright install-deps先装系统依赖再重试。Linux用户特别注意Ubuntu需要sudo apt-get install libnss3 libatk1.0-0 libatk-bridge2.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxdmcp1 libxext6 libxfixes3 libxi6 libxkbcommon0 libxml2 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libasound2 libatk-bridge2.0-0 libatspi2.0-0 libcairo2 libcups2 libdbus-1-3 libdrm2 libgbm1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libpci3 libpulse0 libxcomposite1 libxcursor1 libxdamage1 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 libgbm1 libxshmfence1 libegl1 libgles2否则Playwright启动失败。4.2 Spider配置settings.py里的12个关键参数settings.py是Scrapy的“控制中枢”我们根据校园集市特性调整了12个核心参数# 1. 并发控制校园集市服务器扛不住高并发 CONCURRENT_REQUESTS 8 CONCURRENT_REQUESTS_PER_DOMAIN 4 # 2. 请求延迟模拟真人操作 DOWNLOAD_DELAY 1.5 # 每次请求间隔1.5秒 RANDOMIZE_DOWNLOAD_DELAY True # 在0.5~2倍间随机 # 3. 用户代理池避免被封 USER_AGENT Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 # 启用随机UA扩展 DOWNLOADER_MIDDLEWARES { scrapy.downloadermiddlewares.useragent.UserAgentMiddleware: None, scrapy_user_agents.middlewares.RandomUserAgentMiddleware: 400, } # 4. 反爬应对Referer必须匹配 DEFAULT_REQUEST_HEADERS { Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en-US;q0.8,en;q0.7, Referer: https://zanoh.edu.cn/, # 强制Referer } # 5. Cookie持久化维持登录态 COOKIES_ENABLED True COOKIES_DEBUG True # 开发时开启查看Cookie变化 # 6. 重试机制校园集市偶尔502 RETRY_TIMES 3 RETRY_HTTP_CODES [500, 502, 503, 504, 408, 429] # 7. 下载超时避免卡死 DOWNLOAD_TIMEOUT 30 # 8. 编码处理中文网页常见GBK FEED_EXPORT_ENCODING utf-8 # 如果页面是GBK加这个中间件 DOWNLOADER_MIDDLEWARES.update({ myproject.middlewares.GbkEncodingMiddleware: 300, }) # 9. 日志级别生产环境用WARNING开发用DEBUG LOG_LEVEL INFO LOG_FILE scrapy.log # 10. 爬取深度限制避免爬到无关页面 DEPTH_LIMIT 3 # 11. 自动限速根据响应时间动态调整 AUTOTHROTTLE_ENABLED True AUTOTHROTTLE_START_DELAY 1 AUTOTHROTTLE_MAX_DELAY 3 # 12. Redis配置增量抓取必需 REDIS_URL redis://localhost:6379/0其中AUTOTHROTTLE_ENABLED是神器——它会监控服务器响应时间自动调整并发数。比如当响应时间从200ms升到800msScrapy会把CONCURRENT_REQUESTS从8降到4避免触发反爬。我们实测发现开启后爬虫稳定性提升90%基本告别“Connection refused”错误。4.3 运行与调试从本地测试到分布式部署本地开发阶段我们用scrapy crawl zanoh_spider -a campus_id101 -s LOG_FILEdebug.log启动参数-a传递校区ID-s覆盖settings参数。关键调试技巧检查请求URL在Spider的start_requests里加self.logger.info(fStart crawling campus {self.campus_id})验证Selector用scrapy shell https://zanoh.edu.cn/list/campus/101/page/1进入交互模式直接测试response.css(div.item-card)查看Pipeline流程在Pipeline的process_item里加self.logger.debug(fProcessed item: {item})当本地验证通过就部署到服务器。我们用Scrapyd做分布式管理# 1. 安装Scrapyd pip install scrapyd scrapyd-client # 2. 配置scrapyd.conf [scrapyd] eggs_dir eggs dbs_dir dbs logs_dir logs items_dir items jobs_to_keep 5 max_proc 0 max_proc_per_cpu 4 finished_to_keep 100 # 3. 打包项目 scrapyd-client build # 4. 部署到服务器假设服务器IP是192.168.1.100 scrapyd-client deploy -p zanoh_spider 192.168.1.100:6800部署后通过curl http://192.168.1.100:6800/schedule.json -d projectzanoh_spider -d spiderzanoh_spider -d campus_id101触发远程爬取。Scrapyd会返回任务ID用curl http://192.168.1.100:6800/logs/zanoh_spider/zanoh_spider/任务ID.log实时查看日志。这个方案比手动SSH登录服务器执行命令高效10倍且支持多校区并行爬取——只需发10个curl请求Scrapyd自动分配到不同CPU核心。4.4 数据质量保障建立三层校验体系爬取的数据必须可信我们构建了三层校验第一层Spider内实时校验在parse_item里加入业务规则检查if not item.get(title) or len(item[title]) 5: self.logger.error(fInvalid title for {response.url}) return # 直接丢弃脏数据第二层Pipeline数据清洗校验如前所述价格范围、时间格式、空值处理都在Pipeline完成。第三层Post-Crawl数据审计爬取完成后运行独立审计脚本# audit.py import pandas as pd from sqlalchemy import create_engine engine create_engine(mysqlpymysql://user:passlocalhost/zanoh) df pd.read_sql(SELECT * FROM zanoh_items WHERE created_at DATE_SUB(NOW(), INTERVAL 1 DAY), engine) # 检查异常值 print(价格异常商品:, df[(df[price] 1) | (df[price] 500)].shape[0]) print(时间异常商品:, df[pd.to_datetime(df[publish_time], errorscoerce).isna()].shape[0]) print(重复商品ID:, df[item_id].duplicated().sum()) # 生成质量报告 with open(audit_report.txt, w) as f: f.write(f总记录数: {len(df)}\n) f.write(f价格异常: {df[(df[price] 1) | (df[price] 500)].shape[0]}\n) f.write(f时间异常: {df[pd.to_datetime(df[publish_time], errorscoerce).isna()].shape[0]}\n)这个脚本每天凌晨自动运行邮件发送报告。上线三个月数据准确率保持在99.7%以上教研团队反馈“比人工采集还准”。5. 常见问题与排查技巧实录5.1 403 ForbiddenReferer和User-Agent的组合拳校园集市的反爬第一道关就是403。我们发现单纯换User-Agent没用必须同时伪造Referer。错误做法是只在DEFAULT_REQUEST_HEADERS里设Referer但Scrapy的start_requests会忽略这个设置。正确方案是def start_requests(self): urls [fhttps://zanoh.edu.cn/list/campus/{self.campus_id}/page/1] for url in urls: yield scrapy.Request( urlurl, headers{ Referer: https://zanoh.edu.cn/, User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 }, callbackself.parse_list )更彻底的方案是写一个Middleware自动为所有请求添加Refererclass RefererMiddleware: def process_request(self, request, spider): if zanoh.edu.cn in request.url: request.headers[Referer] https://zanoh.edu.cn/启用这个Middleware后403错误率从35%降到0.2%。关键是Referer必须是目标域名的根路径不能是https://zanoh.edu.cn/list/这种子路径否则服务器校验失败。5.2 动态URL失效从JS中提取真实API地址有些页面的“查看更多”按钮是JS生成的点击后XHR请求/api/more-items?offset20limit20。Scrapy无法监听点击事件但我们能从页面JS里“挖”出API地址def parse_list(self, response): # 查找JS文件链接 js_urls response.css(script[src*bundle]::attr(src)).getall() for js_url in js_urls: full_js_url response.urljoin(js_url) # 下载JS文件并搜索API模式 js_response requests.get(full_js_url) api_match re.search(rfetch\((/api/more-items\?.*?), js_response.text) if api_match: api_url response.urljoin(api_match.group(1)) yield scrapy.Request(urlapi_url, callbackself.parse_api_items)这个技巧让我们绕过前端渲染直击数据源头。实测API响应速度比HTML页面快4倍且数据结构更干净。5.3 Redis连接拒绝权限与端口的隐形陷阱ConnectionRefusedError: [Errno 111] Connection refused是Redis最常见的错误。排查顺序必须是确认Redis服务运行systemctl status redis-serverLinux或redis-server --versionMac检查端口是否监听netstat -tuln | grep 6379如果没输出说明Redis没启动验证防火墙sudo ufw status确保6379端口开放测试连接redis-cli -h 127.0.0.1 -p 6379 ping返回PONG才算通检查密码如果Redis设置了密码REDIS_URL必须是redis://:passwordlocalhost:6379/0我们曾遇到一次诡异问题本地测试正常部署到服务器就报错。最后发现是云服务器安全组没放行6379端口而错误提示和连接超时一模一样。所以永远先ping再查日志。5.4 Playwright渲染空白Chromium沙箱权限问题Linux服务器上Playwright常报TimeoutError: Timeout 30000ms exceeded实际是Chromium沙箱权限不足。解决方案是启动时加参数# 在settings.py里 PLAYWRIGHT_LAUNCH_OPTIONS { headless: True, args: [ --no-sandbox, --disable-setuid-sandbox, --disable-gpu, --disable-dev-shm-usage ] }--no-sandbox是关键它关闭Chromium沙箱虽然安全性略降但在爬虫场景可接受。加上后渲染成功率从45%升到99.8%。5.5 数据丢失Pipeline未返回item的静默故障这是最隐蔽的Bug。当Pipeline里写了逻辑但忘记return itemScrapy会认为数据处理失败直接丢弃。症状是日志显示Scraped from 200 https://...但数据库里没数据。排查方法在Pipeline开头加self.logger.debug(Pipeline started)在结尾加self.logger.debug(Pipeline finished)如果只看到开头日志说明卡在中间某行我们强制规定所有Pipeline必须以return item结尾哪怕只是pass。团队代码审查时第一条就是检查这个。提示Scrapy的LOG_LEVEL DEBUG能暴露所有内部流程但会产生海量日志。建议只在问题时段临时开启用grep pipeline scrapy.log快速定位。注意不要在Pipeline里做耗时操作如调用外部API否则会阻塞整个爬虫队列。必须异步处理的逻辑应移到单独的服务里Pipeline只做轻量清洗。6. 源码结构与可复用模块设计6.1 项目目录树为什么这样组织zanoh_spider/ ├── scrapy.cfg ├── zanoh_spider/ │ ├── __init__.py │ ├── items.py # Item定义业务字段 │ ├── middlewares.py # 自定义中间件Playwright、Referer │ ├── pipelines.py # 数据清洗与存储 │ ├── spiders/ │ │ ├── __init__.py │ │ └── zanoh_spider.py # 主Spider │ ├── settings.py # 全局配置 │ └── utils/ │ ├── __init__.py │ ├── time_parser.py # 时间解析工具 │ └── price_cleaner.py # 价格清洗工具 └── requirements.txt这种结构的核心理念是“关注点分离”。items.py只管数据结构pipelines.py只管数据流转spiders/只管页面逻辑。当需要支持新站点比如“知源二手书”只需新建spiders/zhixuan_spider.py复用items.py和pipelines.py开发时间缩短70%。6.2 可复用工具模块详解utils/time_parser.py是我们提炼的精华from datetime import datetime, timedelta import re def parse_relative_time(text): 解析“2小时前”“昨天”“3天前”等相对时间 if not text: return datetime.now() # 匹配“X小时前” hour_match re.search(r(\d)小时前, text) if hour_match: hours int(hour_match.group(1)) return datetime.now() - timedelta(hourshours) # 匹配“昨天” if 昨天 in text: return datetime.now() - timedelta(days1) # 匹配“X天 p a hrefhttps://download.csdn.net/download/2501_91537435/92481862 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
返回列表