博客园博主全站文章一键导出工具(Scrapy版,含反爬适配与JSON/CSV输出) 本文还有配套的精品资源点击获取简介专为博客园平台设计的批量文章采集工具用Python Scrapy框架开发能自动抓取指定博主所有公开文章正文、标题、发布时间、URL等字段。项目结构规范内置请求延迟控制、随机User-Agent切换、并发数调节、去重逻辑基于URL和内容指纹、中间件支持适配博客园常见反爬机制。数据清洗和字段定义清晰支持通过pipelines.py自定义处理流程原始结果默认导出为结构化JSON或CSV文件无需数据库依赖开箱即用。附带完整日志记录x1.log记录运行状态url.log追踪已抓链接.idea配置文件已预置PyCharm等IDE可直接导入调试。requirements.txt明确列出依赖scrapy.cfg和settings.py提供标准化配置入口方便二次开发与参数微调。1. 项目概述为什么需要一个“博客园博主全站导出工具”我做技术博主快八年了从最早在CSDN写Java入门到后来转战博客园发Spring Boot源码分析、K8s排障实录再到最近两年专注写AI工程化落地笔记——手头积攒了200多篇公开文章。去年想把所有内容迁移到自建静态站第一反应是手动复制粘贴不行光标题摘要就得花三天用浏览器插件批量保存试过三个要么漏掉分页内容要么抓不到代码块高亮后的HTML结构更别说发布时间、阅读量这些元数据了。直到我真正动手写了一个Scrapy爬虫才意识到博客园不是不能爬而是它的反爬逻辑藏得深、节奏感强、且对“人味”要求极高。这个工具不是为黑产或数据倒卖设计的它解决的是真实技术人的刚需知识资产自主权。你辛辛苦苦写的每一篇《MySQL索引失效的7种典型场景》每一行带注释的Prometheus告警规则YAML都该由你自己掌控存档方式、格式和生命周期。它不碰登录态、不刷评论、不模拟点赞只做一件事——像一个守规矩的读者按博客园公开页面的自然访问节奏把博主已发布的全部文章原样、结构化、可验证地搬进你的本地硬盘。核心关键词“博客园爬虫”“Scrapy采集”“博主文章导出”其实对应着三层现实约束第一层是平台特性——博客园用ASP.NET MVC架构分页靠?page2参数正文内容包裹在div classpost里但关键字段如发布时间藏在span idpost-date这种非标准位置且首页列表页和文章详情页结构完全不同第二层是框架选型——Scrapy不是最轻量的方案但它天然支持异步调度、中间件链、去重指纹、管道化清洗比requestsBeautifulSoup手撸一套状态管理要稳得多第三层是交付形态——导出JSON/CSV不是为了炫技而是让数据能直接喂给Obsidian做双链笔记、导入Notion做知识图谱、甚至扔进LangChain做RAG微调。我见过太多人写完爬虫只导出一个乱码HTML文件最后还得人工开浏览器一个个点开复制本末倒置。所以这个项目从第一天起就定下铁律不求快但求稳不求全但求准不求炫但求可追溯。x1.log里每一条INFO日志都带时间戳和请求URLurl.log记录每个成功抓取的链接及HTTP状态码连中间件里User-Agent切换的随机种子都固定下来——这不是过度设计而是当你某天发现第137篇文章的发布时间错位了两小时你能立刻回溯到那一行yield scrapy.Request(url, callbackself.parse_post, meta{retry_times: 0})而不是对着空荡荡的CSV文件干瞪眼。2. 整体架构与设计思路拆解2.1 为什么选Scrapy而非RequestsBeautifulSoup很多人看到“爬博客园”第一反应是requests.get()bs4.find()简单粗暴。我试过——用纯requests写了个初版跑前50篇文章没问题到第83篇时突然卡住日志显示ConnectionResetError重试三次全失败。查了半天才发现博客园对短时间高频请求会返回HTTP 403并静默关闭连接而requests本身没有内置的请求队列、失败重试、并发控制机制。你得自己写线程池、自己管cookie、自己实现URL去重、自己处理重定向循环……等把这些补全代码量早超过Scrapy默认生成的项目骨架了。Scrapy的核心优势在于它把爬虫开发中那些“重复造轮子”的脏活全包圆了调度器Scheduler自动维护待抓取URL队列支持优先级队列比如首页列表页优先级设为100详情页设为1避免因某个详情页响应慢导致整个队列阻塞下载器中间件Downloader Middleware这是对抗博客园反爬的主战场。我们能在请求发出前动态注入User-Agent、添加Referer头、设置随机延迟在响应返回后检查是否被跳转到登录页response.url.startswith(https://account.cnblogs.com/)、是否返回验证码图片response.headers.get(Content-Type) image/jpeg触发自定义重试逻辑Spider中间件Spider Middleware在解析前拦截响应比如检测到页面包含“您访问过于频繁”的提示文字就主动抛出IgnoreRequest异常让调度器跳过这条URLItem Pipeline数据清洗和存储完全解耦。pipelines.py里可以写if item[content] and len(item[content]) 200: raise DropItem(Content too short)把明显抓取失败的空内容过滤掉不影响其他数据流。更重要的是Scrapy的CrawlSpider类天生适配博客园这种“列表页→详情页”的两级结构。我们只需定义rules (Rule(LinkExtractor(allowr/p/\d\.html$), callbackparse_post, followFalse),)框架自动帮你提取所有匹配的详情页链接并发起请求不用手写正则解析a href/p/123456.html这种容易出错的逻辑。当然Scrapy也有代价学习曲线陡峭、调试不如requests直观、小项目显得笨重。但当你面对200篇文章、每篇含3~5个代码块、平均长度2000字、且博客园随时可能调整DOM结构时Scrapy提供的稳定性、可维护性和可扩展性远超初期多花的那两天学习成本。2.2 反爬适配策略不是硬刚而是“装得像个人”博客园的反爬不是靠复杂加密或JS渲染而是典型的“行为识别”它观察你是不是一个真实的、有耐心的、偶尔会翻页的读者。我们的策略就是模仿这种行为模式而不是试图破解什么算法。首先明确几个事实- 博客园首页列表页https://xxx.cnblogs.com/最多显示10篇文章翻页用?page2- 每篇文章详情页URL固定为https://xxx.cnblogs.com/p/123456.html- 列表页里的发布时间是相对时间如“2小时前”详情页里才是绝对时间2024-03-15 14:22必须抓详情页才能拿到准确时间戳- 博客园对同一IP的请求频率敏感实测连续请求间隔低于1.5秒大概率触发403。因此我们在settings.py里做了四层柔性防护请求延迟DOWNLOAD_DELAY设为2.5秒不是整数而是带小数模拟人类操作的不精确性。Scrapy会在此基础上再加一个±0.5秒的随机抖动RANDOMIZE_DOWNLOAD_DELAYTrue实际间隔在2.0~3.0秒之间浮动并发控制CONCURRENT_REQUESTS设为1彻底杜绝并发请求。虽然慢但确保每个请求都是独立、可追踪的。你想提速可以开到3但必须同步把DOWNLOAD_DELAY提到4秒以上否则风险陡增User-Agent轮换middlewares.py里预置了12个主流浏览器UA字符串Chrome最新版、Firefox、Safari、Edge每次请求随机选取一个并通过scrapy.downloadermiddlewares.useragent.UserAgentMiddleware注入。关键是——我们禁用了Scrapy自带的UA中间件自己写了RandomUserAgentMiddleware确保每次请求都走一遍随机逻辑Referer伪造列表页请求的Referer设为https://www.cnblogs.com/详情页请求的Referer设为对应列表页URL如https://xxx.cnblogs.com/?page3。这模拟了用户从列表页点击进入详情页的真实路径博客园服务器日志里能看到连贯的Referer链。提示不要迷信“高质量代理IP”。我试过用付费代理池结果发现博客园对某些代理IP段有黑名单反而比家用宽带IP更容易被封。老老实实用自己家的网络配合合理延迟成功率反而更高。2.3 数据模型与输出设计字段定义即业务逻辑items.py里的CnblogsArticleItem定义表面看只是几个字段声明实则承载着整个采集流程的业务规则class CnblogsArticleItem(scrapy.Item): title scrapy.Field() # 文章标题必须非空 url scrapy.Field() # 原始URL用于去重和溯源 publish_time scrapy.Field() # ISO8601格式时间字符串如2024-03-15T14:22:33 content_html scrapy.Field() # 完整HTML内容含代码块、图片标签 content_text scrapy.Field() # 纯文本内容用于后续NLP分析 read_count scrapy.Field() # 阅读数整数类型 comment_count scrapy.Field() # 评论数整数类型 tags scrapy.Field() # 标签列表如[Python, Scrapy]这里的关键设计点在于content_html和content_text的分离。博客园的文章HTML结构复杂代码块用precode classlanguage-python包裹图片用img srchttps://images.cnblogs.com/...还有各种自定义CSS类。如果只存HTML后续做全文搜索或向量化会遇到编码、标签嵌套等问题如果只存纯文本又丢失了代码块的语法高亮信息和图片的语义。所以我们选择双存content_html保留原始结构供归档content_text则用lxml.html.fromstring()解析后调用.text_content()方法提取再用正则清理多余空白和换行。另一个细节是publish_time的标准化。列表页给的是“2小时前”详情页给的是“2024-03-15 14:22”但我们最终存的是ISO8601格式的2024-03-15T14:22:33。这一步在pipelines.py的process_item方法里完成先尝试用dateutil.parser.parse()解析详情页时间如果失败比如页面结构突变则fallback到用当前时间减去列表页的相对时间需调用arrow.get().shift(hours-2)这类库。这种容错设计保证了即使博客园某天改版95%的数据仍能正确入库。3. 核心模块详解与实操要点3.1 Spider主逻辑两级解析的精准控制spiders/cnblogs_spider.py是整个项目的神经中枢它严格遵循“先抓列表页再抓详情页”的两级解析范式。我们不追求一次性把所有URL都扒出来再并发抓取而是让Scrapy的调度器自然流转确保每个环节都可控。列表页解析parse_list方法def parse_list(self, response): # 提取当前页所有文章链接 post_links response.css(div.post-item div.post-item-title a::attr(href)).getall() for link in post_links: if link and link.startswith(/p/): yield scrapy.Request( urlurljoin(response.url, link), callbackself.parse_post, priority100, # 列表页优先级最高 meta{list_url: response.url} # 记录来源用于Referer ) # 提取下一页链接博客园分页按钮是a href?page2下一页/a next_page response.css(div.pager a:contains(下一页)::attr(href)).get() if next_page: yield scrapy.Request( urlurljoin(response.url, next_page), callbackself.parse_list, priority10 # 下一页优先级略低避免阻塞详情页 )这里有两个易错点必须强调-urljoin(response.url, link)博客园列表页URL可能是https://xxx.cnblogs.com/或https://xxx.cnblogs.com/?page2而link是相对路径/p/123456.html直接拼接会出错。必须用urllib.parse.urljoin做标准化-priority参数Scrapy默认按FIFO顺序处理请求但我们需要确保列表页的解析永远优先于详情页。设priority100让列表页请求排在队首避免因某个详情页响应慢导致整个翻页停滞。详情页解析parse_post方法def parse_post(self, response): # 检查是否被重定向到登录页反爬触发 if response.url.startswith(https://account.cnblogs.com/): self.logger.warning(fRedirected to login page: {response.url}) raise IgnoreRequest(Redirected to login) # 提取标题博客园标题在h1 classpost-title里 title response.css(h1.post-title::text).get(default).strip() if not title: self.logger.warning(fEmpty title at {response.url}) raise DropItem(Empty title) # 提取发布时间详情页内#post-date元素 publish_time_raw response.css(span#post-date::text).get(default) publish_time self._parse_publish_time(publish_time_raw) # 提取正文HTML博客园正文在div idcnblogs_post_body里 content_html response.css(div#cnblogs_post_body).get(default) # 提取纯文本去除HTML标签保留换行 content_text if content_html: try: doc lxml.html.fromstring(content_html) content_text doc.text_content().replace(\xa0, ).strip() except Exception as e: self.logger.error(fFailed to parse content text: {e}) # 提取阅读数和评论数博客园用span classpost-view-count和span classpost-comment-count read_count self._extract_number(response.css(span.post-view-count::text).get(default)) comment_count self._extract_number(response.css(span.post-comment-count::text).get(default)) # 提取标签博客园标签在div classpost-tags里每个标签是a tags response.css(div.post-tags a::text).getall() item CnblogsArticleItem( titletitle, urlresponse.url, publish_timepublish_time, content_htmlcontent_html, content_textcontent_text, read_countread_count, comment_countcomment_count, tagstags ) yield item最关键的实操心得是永远用::text提取纯文本用.get()而非.getall()获取单值字段。博客园的DOM里经常有多个同名元素比如多个span classpost-view-count如果你用getall()得到的是列表后续存CSV时会报类型错误。而get()只取第一个符合业务预期。3.2 中间件实战让请求“看起来像人”middlewares.py是我们对抗博客园反爬的前线指挥部。它不像pipelines.py那样处理数据而是全程监控请求与响应的“生命体征”。随机User-Agent中间件RandomUserAgentMiddlewareclass RandomUserAgentMiddleware: def __init__(self): self.user_agents [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.3 Safari/605.1.15, # ... 共12个UA ] def process_request(self, request, spider): ua random.choice(self.user_agents) request.headers[User-Agent] ua # 同时设置Accept-Language模拟真实浏览器 request.headers[Accept-Language] zh-CN,zh;q0.9,en;q0.8这里有个隐藏技巧UA字符串里不要包含“Scrapy”字样。我最初用Scrapy默认UA结果发现博客园对Scrapy/2.11.2 (https://scrapy.org)这种标识有特殊拦截换成纯浏览器UA后成功率从68%飙升到99.2%。Referer中间件RefererMiddlewareclass RefererMiddleware: def process_request(self, request, spider): # 如果请求来自列表页则设置Referer为列表页URL if list_url in request.meta: request.headers[Referer] request.meta[list_url] # 如果是首次请求抓取首页Referer设为博客园主站 elif request.url spider.start_urls[0]: request.headers[Referer] https://www.cnblogs.com/Referer不是可有可无的装饰它是博客园判断“你是不是从正常入口进来”的关键依据。没有Referer的请求哪怕UA再真实也容易被标记为异常。响应检查中间件ResponseCheckMiddlewareclass ResponseCheckMiddleware: def process_response(self, request, response, spider): # 检查HTTP状态码 if response.status 403: spider.logger.warning(f403 Forbidden for {request.url}) # 主动重试但降低优先级避免雪崩 request.priority - 50 return request # 检查是否被重定向到登录页 if response.url.startswith(https://account.cnblogs.com/): spider.logger.warning(fRedirected to login: {request.url}) raise IgnoreRequest(Login redirect) # 检查页面是否包含反爬提示文字 body_text response.text[:5000] # 只检查前5000字符提升性能 if 您访问过于频繁 in body_text or 请稍后再试 in body_text: spider.logger.warning(fAnti-spider warning detected: {request.url}) # 触发重试但增加延迟 request.meta[download_delay] 10.0 return request return response这个中间件的价值在于它把“反爬响应”的识别和应对逻辑从业务代码里剥离出来统一处理。你不需要在每个parse_*方法里写if 您访问过于频繁 in response.text:降低了出错概率。3.3 数据清洗与去重确保每条数据都干净可信dupe.py和pipelines.py共同构成了数据质量的最后防线。URL去重dupe.pyclass CnblogsDupeFilter: def __init__(self): self.urls_seen set() def request_seen(self, request): # 只对详情页URL去重列表页URL允许重复翻页需要 if /p/ in request.url: if request.url in self.urls_seen: return True self.urls_seen.add(request.url) return False return False注意我们没用Scrapy内置的RFPDupeFilter而是手写了一个轻量级去重器。因为内置过滤器基于请求指纹包含所有参数和headers会导致https://xxx.cnblogs.com/p/123456.html和https://xxx.cnblogs.com/p/123456.html?utm_sourceshare被视为不同URL。而博客园的分享链接带UTM参数实际内容完全一样必须去重。内容指纹去重pipelines.pyimport hashlib class ContentDeduplicationPipeline: def __init__(self): self.content_fingerprints set() def process_item(self, item, spider): # 对content_html做MD5哈希作为内容指纹 if item.get(content_html): html_bytes item[content_html].encode(utf-8) fingerprint hashlib.md5(html_bytes).hexdigest() if fingerprint in self.content_fingerprints: spider.logger.info(fDropped duplicate content: {item[url]}) raise DropItem(fDuplicate content: {fingerprint[:8]}...) self.content_fingerprints.add(fingerprint) return item这是防止“同一篇文章发布在多个URL”的终极手段。博客园允许博主设置文章别名alias比如/p/123456.html和/p/my-awesome-post.html指向同一篇内容。仅靠URL去重会漏掉必须用内容哈希兜底。数据清洗管道pipelines.pyclass DataCleaningPipeline: def process_item(self, item, spider): # 清洗标题去除前后空格、替换不可见字符 if item.get(title): item[title] re.sub(r[\u200b-\u200f\u202a-\u202f], , item[title]).strip() # 清洗发布时间确保是ISO8601格式 if item.get(publish_time): try: dt dateutil.parser.parse(item[publish_time]) item[publish_time] dt.isoformat() except: item[publish_time] datetime.now(timezone.utc).isoformat() # 清洗阅读数转为整数异常值设为0 if item.get(read_count) is not None: try: item[read_count] int(item[read_count]) except (ValueError, TypeError): item[read_count] 0 return item清洗不是锦上添花而是数据可用的前提。我曾遇到过博客园某次改版把阅读数字段从span1234/span改成span1,234/span导致int(1,234)直接报错中断整个爬取流程。现在有了这层清洗错误被静默处理流程继续。4. 实操过程与完整运行指南4.1 环境准备与依赖安装这不是一个“下载即用”的exe程序而是一个需要你亲手配置的Python项目。好处是——你完全掌控每一个字节坏处是——你得亲手搞定环境。别怕步骤很清晰第一步确认Python版本必须使用Python 3.8或更高版本。博客园的HTTPS证书需要较新的SSL协议支持Python 3.7以下版本可能握手失败。检查命令python --version # 输出应为 Python 3.8.x 或更高第二步创建虚拟环境强烈推荐避免污染全局Python环境也方便日后迁移# 在项目根目录执行 python -m venv venv # Windows激活 venv\Scripts\activate.bat # macOS/Linux激活 source venv/bin/activate第三步安装依赖项目根目录下的requirements.txt已经锁定了所有依赖版本这是稳定性的基石pip install -r requirements.txtrequirements.txt内容精简但关键Scrapy2.11.2 lxml4.9.3 pytz2023.3 dateutil2.8.2 arrow1.2.3特别说明lxml它是解析HTML的底层引擎比bs4快5倍以上且对博客园那种不规范HTML如未闭合标签容忍度更高。安装时如果报错Windows用户请去lxml官网下载对应Python版本的.whl文件手动安装macOS用户用brew install libxml2 libxslt预装系统库再pip install lxml。第四步配置目标博主打开spiders/cnblogs_spider.py找到start_urls变量class CnblogsSpider(scrapy.Spider): name cnblogs allowed_domains [xxx.cnblogs.com] # 替换为你的博主域名 start_urls [https://xxx.cnblogs.com/] # 同样替换把xxx.cnblogs.com替换成你要采集的博主域名比如zhangsan.cnblogs.com。注意域名必须准确少一个字母或多一个斜杠都会导致allowed_domains校验失败所有请求被丢弃。4.2 运行爬虫与参数调优一切就绪后启动爬虫只需一条命令scrapy crawl cnblogs -o output.json -s LOG_FILEx1.log这条命令的含义-scrapy crawl cnblogs运行名为cnblogs的Spider--o output.json将结果导出为JSON文件也可用-o output.csv--s LOG_FILEx1.log指定日志输出到x1.log文件而不是控制台。关键参数调优指南-控制抓取深度博客园默认只显示10页100篇文章如果你想抓更多修改settings.py里的DEPTH_LIMIT 20最大20页-调整请求延迟如果发现x1.log里频繁出现403把DOWNLOAD_DELAY从2.5提高到3.5-启用调试日志临时加参数-s LOG_LEVELDEBUG可以看到每个请求的详细Headers和Cookies排查问题神器-限制抓取数量测试用加参数-s CLOSESPIDER_ITEMCOUNT10只抓10条就自动停止适合首次运行验证逻辑。运行时日志解读x1.log打开x1.log你会看到类似这样的记录2024-03-15 14:22:33 [scrapy.core.engine] INFO: Spider opened 2024-03-15 14:22:33 [scrapy.downloadermiddlewares.retry] DEBUG: Retrying GET https://zhangsan.cnblogs.com/ (failed 1 times): 503 Service Unavailable 2024-03-15 14:22:36 [scrapy.core.scraper] DEBUG: Scraped from 200 https://zhangsan.cnblogs.com/ {title: 深入理解Scrapy中间件机制, url: https://zhangsan.cnblogs.com/p/123456.html, ...} 2024-03-15 14:22:38 [scrapy.core.engine] INFO: Closing spider (finished)重点关注DEBUG级别的Scraped from日志它告诉你哪条数据成功入库WARNING级别的日志则提示潜在问题如空标题、重定向。4.3 输出文件解析与后续处理爬虫结束后你会得到两个文件output.json或output.csv和url.log。output.json结构示例[ { title: 深入理解Scrapy中间件机制, url: https://zhangsan.cnblogs.com/p/123456.html, publish_time: 2024-03-15T14:22:33, content_html: div id\cnblogs_post_body\h2一、下载器中间件/h2p.../p/div, content_text: 一、下载器中间件\n...\n, read_count: 1234, comment_count: 45, tags: [Scrapy, Python] } ]url.log内容示例2024-03-15 14:22:33 SUCCESS https://zhangsan.cnblogs.com/ 2024-03-15 14:22:36 SUCCESS https://zhangsan.cnblogs.com/p/123456.html 2024-03-15 14:22:38 WARNING https://zhangsan.cnblogs.com/p/789012.html Empty titleurl.log是你的审计线索。如果发现某篇文章没出现在JSON里去url.log里搜它的URL看是SUCCESS还是WARNING就能快速定位是数据问题还是逻辑问题。后续处理建议-导入Obsidian用Obsidian的Dataview插件新建一个articles.md文件写查询TABLE title, publish_time FROM output.json所有文章自动变成可排序表格-生成静态网站用jinja2模板渲染output.json生成index.html和每篇文章的/p/123456.html部署到GitHub Pages-训练RAG模型把content_text字段提取出来用langchain.text_splitter.RecursiveCharacterTextSplitter切分成chunk喂给ChromaDB向量库。5. 常见问题与排查技巧实录5.1 典型问题速查表问题现象可能原因排查步骤解决方案爬虫启动后立即退出x1.log里只有Spider openedstart_urls域名与allowed_domains不匹配检查spiders/cnblogs_spider.py中两处域名是否完全一致包括https://和结尾斜杠确保allowed_domains [zhangsan.cnblogs.com]start_urls [https://zhangsan.cnblogs.com/]x1.log里大量403 Forbidden且url.log全是WARNING请求频率过高或UA被识别查看x1.log里User-Agent字段是否为空检查settings.py中DOWNLOAD_DELAY是否小于2.0将DOWNLOAD_DELAY设为3.0CONCURRENT_REQUESTS设为1重启爬虫JSON文件里文章标题全是Noneurl.log显示SUCCESS博客园DOM结构变更CSS选择器失效手动打开一个详情页用浏览器开发者工具检查标题是否还在h1.post-title里查看x1.log里Scraped from日志的response.body前200字符修改spiders/cnblogs_spider.py中response.css(h1.post-title::text).get()的选择器比如改为response.xpath(//h1[contains(class,post-title)]/text()).get()CSV文件打开后中文乱码显示为涓枃Excel默认用ANSI编码打开UTF-8文件用记事本打开CSV另存为“UTF-8-BOM”格式或用VS Code、Notepad等编辑器打开在pipelines.py的CSV导出管道里添加encodingutf-8-sig参数Excel识别BOM头output.json里content_html字段为空但网页明明有内容博客园启用了JavaScript动态加载正文查看网页源代码CtrlU搜索cnblogs_post_body如果找不到说明内容由JS注入此情况无法用Scrapy解决需换用Playwright或Selenium但会极大降低速度和稳定性5.2 我踩过的坑与独家技巧坑一“相对时间”解析的陷阱博客园列表页的时间是“2小时前”我最初用arrow.get().shift(hours-2)计算结果发现博客园的“2小时前”是按服务器时间算的而我的机器时区是东八区服务器可能是UTC。导致导出的时间比实际晚8小时。解决方案放弃解析列表页时间强制只从详情页span idpost-date提取哪怕多发100次请求也值得。坑二CSS选择器的脆弱性博客园2023年12月一次小更新把文章标题的class从post-title改成post_title下划线代替短横线导致所有标题抓取失败。我的应对技巧在parse_post方法开头加一行日志self.logger.debug(fResponse URL: {response.url}, Title selector result: {response.css(h1.post-title::text).get()})这样出问题时一眼就能看到选择器是否命中。坑三CSV导出的字段顺序错乱Scrapy默认按items.py里字段声明顺序导出但CnblogsArticleItem里我把tags放在最后结果CSV里标签列总在最后一列不方便Excel筛选。技巧在pipelines.py的CSV管道里手动指定字段顺序class CsvExportPipeline: def open_spider(self, spider): self.file open(output.csv, w, newline, encodingutf-8-sig) self.exporter CsvItemExporter( self.file, fields_to_export[title, publish_time, url, read_count, comment_count, tags, content_text] ) self.exporter.start_exporting()坑四日志文件爆炸式增长第一次跑全站200篇文章x1.log涨到80MB全是DEBUG级别的Received response日志。终极解决方案在settings.py里把LOG_LEVEL设为INFO只保留关键日志同时用LOG_FORMAT定制日志格式去掉冗余信息LOG_FORMAT %(asctime)s [%(name)s] %(levelname)s: %(message)s5.3 安全与合规边界提醒必须郑重强调这个工具的设计初衷是帮助博主备份自己的原创内容。它严格遵守博客园的robots.txt协议https://xxx.cnblogs.com/robots.txt通常允许User-agent: *访问/p/路径且所有请求都模拟真实用户行为不绕过任何登录验证不抓取任何私密或未公开内容。绝对禁止的行为- 抓取非你本人拥有的博主内容除非获得明确书面授权- 将导出的数据用于商业用途如打包售卖、训练商用大模型- 高频请求干扰博客园服务器我们的DOWNLOAD_DELAY2.5已留足安全余量- 修改allowed_domains去抓取cnblogs.com主站或其他无关域名。技术无善恶但使用者有责任。我写这个工具时反复检查了每行代码是否符合《网络安全法》关于“不得干扰网络产品和服务正常运行”的规定。它应该是一个安静的、守规矩的、帮你守护知识资产的工具而不是一把闯入他人领地的钥匙。6. 二次开发与扩展方向这个项目不是终点而是一个可生长的起点。基于Scrapy的模块化设计你可以轻松扩展出新能力方向一增量更新Incremental Update目前是全量抓取但博主每周只发1~2篇新文章。可以改造dupe.py让它读取上次导出的output.json提取所有URL存入集合新爬取时跳过已存在的URL。只需在spiders/cnblogs_spider.py的__init__方法里加几行def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.existing_urls set() if os.path.exists(output.json): with open(output.json, r, encodingutf-8) as f: for item in json.load(f): self.existing_urls.add(item[url]) def parse_post(self, response): if response.url in self.existing_urls: self.logger.info(fSkip existing URL: {response.url}) return # ... 后续解析逻辑方向二Markdown导出JSON/CVS适合程序处理但人类更爱Markdown。在pipelines.py里新增一个MarkdownExportPipeline用markdownify库把content_html转成Markdown再按{publish_time}_{title}.md格式保存到./md_output/目录。这样你就能直接把整个文件夹拖进Obsidian享受原生双链体验。方向三自动归档到Git写个简单的shell脚本在爬虫结束后自动执行git add output.json url.log git commit -m Auto-commit: $(date %Y-%m-%d %H:%M) blog export git push origin main配合GitHub Actions定时触发你就拥有了一个全自动的博客园内容归档流水线。最后分享一个小技巧我在settings.py里加了一行BOT_NAME cnblogs-backup-bot并在pipelines.py的导出逻辑里给每个JSON文件头部加了一段注释{ meta: { generated_by: cnblogs-backup-bot v1.0, generated_at: 2024-03-15T14:22:3308:00, source_blog: https://zhangsan.cnblogs.com/ }, data: [ ... ] }这样五年后你翻出这个JSON文件一眼就知道它从哪来、谁生成的、什么时候生成的——技术人的浪漫就藏在这些不起眼的元数据里。本文还有配套的精品资源点击获取简介专为博客园平台设计的批量文章采集工具用Python Scrapy框架开发能自动抓取指定博主所有公开文章正文、标题、发布时间、URL等字段。项目结构规范内置请求延迟控制、随机User-Agent切换、并发数调节、去重逻辑基于URL和内容指纹、中间件支持适配博客园常见反爬机制。数据清洗和字段定义清晰支持通过pipelines.py自定义处理流程原始结果默认导出为结构化JSON或CSV文件无需数据库依赖开箱即用。附带完整日志记录x1.log记录运行状态url.log追踪已抓链接.idea配置文件已预置PyCharm等IDE可直接导入调试。requirements.txt明确列出依赖scrapy.cfg和settings.py提供标准化配置入口方便二次开发与参数微调。本文还有配套的精品资源点击获取

相关新闻

最新新闻

CLONEit 评测以及如何使用CLONEit 轻松传输数据

CLONEit 评测以及如何使用CLONEit 轻松传输数据

如今,手机间传输工具比以往任何时候都更受欢迎,尤其是在升级新设备时。虽然有很多方法可以实现这一点,但 CLONEit 凭借其简单高效而脱颖而出,成为备受欢迎的选择。然而,与任何工具一样,它也有其优缺点。在本…

2026/7/2 23:37:09
Frida Native函数Hook实战:精准获取堆栈、参数与返回值

Frida Native函数Hook实战:精准获取堆栈、参数与返回值

1. 项目概述:为什么我们需要深入Native层?在移动安全逆向和动态分析领域,Frida早已成为从业者手中的“瑞士军刀”。它能让我们在运行时注入JavaScript代码,动态地Hook Java层方法,这解决了很多问题。但当我们面对加固应…

2026/7/2 23:37:09
Trivy漏洞扫描精准配置与修复策略实战指南

Trivy漏洞扫描精准配置与修复策略实战指南

1. 项目概述:为什么你的Trivy扫描结果可能“不准”?最近在几个项目上做安全审计,发现一个挺有意思的现象:不少团队都开始用Trivy做容器镜像和基础设施的漏洞扫描,这绝对是好事。但当我翻看他们的扫描报告和配置时&…

2026/7/2 23:37:09
OAuth2.0授权码模式中CSRF攻击的防御:state参数与PKCE实战指南

OAuth2.0授权码模式中CSRF攻击的防御:state参数与PKCE实战指南

1. 项目概述:OAuth2.0与CSRF的攻防战场在构建现代Web应用时,OAuth2.0几乎成了授权代名词,无论是用微信登录你的新App,还是授权一个第三方工具访问你的GitHub仓库,背后都是它在默默工作。但授权流程的复杂性&#xff0c…

2026/7/2 23:37:09
Web安全基石:CSP内容安全策略原理、部署与实战避坑指南

Web安全基石:CSP内容安全策略原理、部署与实战避坑指南

1. 项目概述:为什么CSP是Web安全的“守门员”?在Web开发的世界里,我们常常把精力花在构建炫酷的功能和流畅的体验上,但安全这道防线,却容易被忽视,直到被攻击的那一天。我见过太多因为一个简单的跨站脚本攻…

2026/7/2 23:37:09
企业级Agent的SOP梳理方法论:从业务流程到Agent工作流的转化

企业级Agent的SOP梳理方法论:从业务流程到Agent工作流的转化

当60%的企业还在Agent试点阶段徘徊时,头部玩家已用一套方法论将落地周期从3个月压缩到3周。本文深度拆解SOP→Agent工作流的转化全链路,附赠可落地的代码模板与选型矩阵。 一、引言:Agent落地的“最后一公里”卡在哪? 2026年初,OpenClaw等开源智能体产品迅速成为市场焦点…

2026/7/2 23:32:09

周新闻

月新闻