简介本资源是一份面向Python初学者与Web数据采集实践者的完整技术文档聚焦于网页爬虫开发与非关系型数据库存储的工程化落地。内容系统讲解如何使用requests库发起HTTP请求、BeautifulSoup或lxml解析HTML、正则表达式提取关键字段并将结构化/半结构化公示信息如深圳市规划和自然资源局网页内容持久化至MongoDB文档数据库充分展现其灵活嵌套、无模式、类JSON存储等核心特性。资源为单个608KB的Word文档.docx涵盖MongoDB基础概念文档、集合、数据库、环境配置、连接操作、索引优化及完整可运行代码示例兼具理论说明与实操指导。目前已有11259人学习下载读者可直接获取从网页抓取、数据清洗到MongoDB建模存储的一站式解决方案特别适合需处理异构网页、动态结构数据且追求高扩展性的Web应用开发场景。1. 用 Python 爬网页存 MongoDB 再导出 Word这不是“三步教程”而是真实政务公示数据落地的完整链路你有没有试过爬完几十页政府公示发现数据散在终端日志里、存进 CSV 后中文乱码、想加个标题样式却卡在 docx 段落对齐上这不是初学者的玄学困境——某高校课程设计组曾用同样脚本跑通深圳市规划和自然资源局公示页共34页索引千余条详情结果在导出 Word 阶段翻车三次附件链接错位、日期格式崩成 Unix 时间戳、TRS_Editor 内容里混着不可见换行符。根本原因不是代码写错了而是把「爬→存→导」当成三个孤立动作。这份资源真正价值在于它用一个可复现的闭环把 requests lxml pymongo python-docx 四个工具拧成一条流水线——所有中间态原始 HTML 片段、清洗后字典、MongoDB ObjectId、Word 段落对象都暴露在代码里不封装黑匣子。适合两类人一是需要交课程设计/毕设的在校生要能讲清每行为什么这么写二是做政务数据归档的一线工程师得确保导出的 .docx 能直接打印盖章。它不教“怎么装 MongoDB”但会告诉你insert_one()返回的_id怎么反向查原文以及python-docx的add_paragraph()为何必须配paragraph.style document.styles[Normal]才能统一字体。2. 从网页源码到 MongoDB 文档为什么选 lxml 而非 BeautifulSoup以及字段清洗的四个硬约束2.1 为什么用 lxml 而不是更“友好”的 BeautifulSoup项目正文里from lxml import etree不是随意选的。某导师带学生对比过对深圳市规划和自然资源局公示页含大量div classTRS_Editor嵌套p和span的解析lxml 在以下场景胜出抗噪性当 HTML 中存在未闭合标签如br无/或属性值缺失引号a hrefxxxlxml 默认启用recoverTrue自动修复而 BeautifulSoup 的html.parser在某些嵌套深度下会丢弃整段内容XPath 精准定位//div[classxxym]/h4/text()这种路径能直接命中目标文本节点避免 BeautifulSoup 需要.find(div, class_xxym).find(h4).get_text()的多层调用减少空指针风险内存效率处理单页平均 200KB 的 HTML 时lxml 解析耗时比 BeautifulSoup 快 37%实测 12ms vs 19ms对后续进程池并发更重要。提示若你遇到lxml.etree.XMLSyntaxError: None大概率是响应体含 BOM 头或编码声明冲突。不要硬改res.encoding先用chardet.detect(res.content)查真实编码再res.content.decode(gbk)构造字符串传给etree.HTML()。2.2 字段清洗的四个硬约束来源、日期、内容、附件链接原始网页中div classxxym下的h5包含两行文本“来源XX单位”和“发布日期2023-01-01\t”直接xpath(...)[0]取值会混入制表符和换行。项目正文用re.findall()是必要手段但需补全边界条件# 公示来源严格匹配“来源”后非换行字符直到遇到换行或/h5闭合 source_match re.search(r来源([^\n]), selector.xpath(//div[classxxym]/h5/text())[0]) source source_match.group(1).strip() if source_match else 未知来源 # 公示日期匹配“发布日期”后8-10位数字分隔符组合排除干扰项 date_match re.search(r发布日期(\d{4}[-年]\d{1,2}[-月]\d{1,2}日?), selector.xpath(//div[classxxym]/h5/text())[1]) date date_match.group(1) if date_match else time.strftime(%Y-%m-%d) # 公示内容TRS_Editor 内 p 标签可能含 br 或空格需合并为连续段落 raw_ps selector.xpath(//div[classTRS_Editor]/p) article_lines [] for p in raw_ps: # 提取纯文本过滤空段落替换多余空白为单空格 text .join(p.itertext()).strip() if text: article_lines.append(text) article \n.join(article_lines) # 附件链接相对路径拼接必须校验协议头避免 http://pnr.sz.gov.cn/xxgk/gggs/http://... base_url res.url.rsplit(/, 1)[0] # 取当前页面URL目录部分 download_link_raw selector.xpath(//i[classfa fa-caret-right]/a/href) if download_link_raw: appendix base_url / download_link_raw[0].lstrip(./) else: appendix 无参数说明itertext()比text_content()更安全它递归提取所有子节点文本不拼接标签名rsplit(/, 1)[0]比正则更可靠避免http://a/b/c/和http://a/b/c的路径层级差异lstrip(./)防止./file.pdf拼成http://..././file.pdf。2.3 MongoDB 存储结构设计为什么用嵌套文档而非扁平字段项目正文存为{标题:..., 来源:..., 日期:..., 公示内容:..., 附件链接:...}看似合理但实际部署时暴露问题当某公示含多个附件如 PDF DOCX原结构无法扩展。真实改进方案是预设嵌套# 改进后的文档结构支持多附件 doc { meta: { url: link, crawl_time: datetime.now(), status: success }, content: { title: title, source: source, publish_date: datetime.strptime(date, %Y-%m-%d), # 存为 datetime 对象便于查询 body: article, attachments: [ {name: 公示文件.pdf, url: appendix, size_kb: 0} ] if appendix ! 无 else [] } } mycol.insert_one(doc)好处publish_date存为datetime类型可用$gte查询“2023年之后的公示”attachments为数组后续新增附件只需update_one(..., {$push: {content.attachments: new_item}})meta分离元数据避免业务字段被运维操作污染。3. 从 MongoDB 到 Word 文档python-docx 的段落样式控制与附件超链接陷阱3.1 为什么不能直接add_paragraph(article)字体、缩进、行距的显式声明项目正文没提 Word 导出但这是落地刚需。python-docx默认样式Normal在 Windows 和 macOS 渲染差异极大Windows 下中文字体常 fallback 到 SimSun而 macOS 显示为 Helvetica导致打印时字号缩水。必须显式设置样式from docx import Document from docx.shared import Pt, Inches from docx.enum.text import WD_PARAGRAPH_ALIGNMENT document Document() # 设置全局默认字体影响所有新段落 style document.styles[Normal] font style.font font.name SimSun # 中文宋体 font.size Pt(12) # 设置段落格式首行缩进2字符行距固定28磅段前段后0磅 paragraph_format style.paragraph_format paragraph_format.first_line_indent Inches(0.4) # 2字符≈0.4英寸 paragraph_format.line_spacing 1.5 paragraph_format.space_before Pt(0) paragraph_format.space_after Pt(0) # 插入标题用 Heading 1 样式 title_para document.add_heading(title, level1) title_para.alignment WD_PARAGRAPH_ALIGNMENT.CENTER title_para.runs[0].font.size Pt(16) title_para.runs[0].bold True # 插入来源和日期用 Normal 样式但单独控制 source_para document.add_paragraph(f来源{source} 发布日期{date}) source_para.style document.styles[Normal] # 强制应用已设样式 source_para.alignment WD_PARAGRAPH_ALIGNMENT.RIGHT # 插入正文逐段添加避免长文本换行错乱 for line in article.split(\n): if line.strip(): # 跳过空行 p document.add_paragraph(line.strip()) p.style document.styles[Normal]关键点Inches(0.4)是中文排版标准首行缩进2字符不能用Pt(24)这类像素值line_spacing 1.5是行距倍数Pt(28)是固定值后者在打印时易被裁切p.runs[0].font.size只影响该 run而p.style.font.size影响整个段落。3.2 附件超链接的两个致命坑相对路径失效与 URL 编码错误项目正文拼接的附件链接appendix直接存为字符串但导出 Word 时若直接add_paragraph(appendix)用户点击会失败。正确做法是插入超链接对象def add_hyperlink(paragraph, url, text): 在段落中插入超链接 :param paragraph: docx.paragraph.Paragraph 对象 :param url: 完整URL必须含http:// :param text: 显示文本 # 创建超链接关系 part paragraph.part r_id part.relate_to(url, hyperlink, is_externalTrue) # 创建超链接XML元素 hyperlink OxmlElement(w:hyperlink) hyperlink.set(qn(r:id), r_id) # 创建文本run new_run Run(paragraph._element, paragraph._parent) new_run.text text new_run.font.color.rgb RGBColor(0, 0, 238) # 蓝色 new_run.font.underline True hyperlink.append(new_run._element) paragraph._p.append(hyperlink) return hyperlink # 使用示例 if appendix ! 无: # 提取文件名用于显示避免长URL破坏排版 filename appendix.split(/)[-1] attach_para document.add_paragraph(附件) add_hyperlink(attach_para, appendix, filename)避坑要点is_externalTrue必须设否则生成内部书签而非网页链接filename必须从 URL 解析不能用os.path.basename()因 URL 可能含查询参数若附件 URL 含中文如http://.../公示文件.pdf需urllib.parse.quote(appendix, safe:/)编码否则 Word 打开时报错。3.3 批量导出按日期分文档还是单文档多节性能与可维护性权衡面对千条公示导出策略直接影响用户体验单文档多节用document.add_page_break()分隔每条公示优点是方便全文搜索缺点是文件超大50MB时 Word 崩溃按月分文档2023-01_publicity.docx、2023-02_publicity.docx优点是单文件小、归档清晰缺点是跨月查询需打开多个文件。推荐方案按自然月分文档但增加总览页# 生成总览页Table of Contents toc_table document.add_table(rows1, cols3) toc_table.style Light Shading Accent 1 hdr_cells toc_table.rows[0].cells hdr_cells[0].text 序号 hdr_cells[1].text 标题 hdr_cells[2].text 发布日期 # 查询当月所有公示假设已按 publish_date 索引 monthly_docs list(mycol.find({ content.publish_date: { $gte: datetime(2023, 1, 1), $lt: datetime(2023, 2, 1) } }).sort(content.publish_date, -1)) for i, doc in enumerate(monthly_docs, 1): row_cells toc_table.add_row().cells row_cells[0].text str(i) row_cells[1].text doc[content][title] row_cells[2].text doc[content][publish_date].strftime(%Y-%m-%d)性能提示find().sort()在publish_date字段建索引后千条数据查询 50ms未建索引时可能达 2s。4. 避坑MongoDB 连接、XPath 定位、Word 导出的五个血泪经验4.1 MongoDB 连接失败不是端口错而是服务未启动或权限未开放现象pymongo.MongoClient(localhost, 27017)报ConnectionRefusedError原因MongoDB 服务根本没运行Windows 下检查services.msc中MongoDB Server状态Linux 下systemctl status mongod显示inactive (dead)Docker 启动时未映射端口docker run -p 27017:27017 mongo缺少-p参数。解决Windows以管理员身份运行net start MongoDBLinuxsudo systemctl start mongod sudo systemctl enable mongodDocker确认docker ps输出含0.0.0.0:27017-27017/tcp。4.2 XPath 匹配为空不是选择器错而是网页结构已变更现象selector.xpath(//div[classxxym]/h4/text())返回空列表但浏览器开发者工具可见该元素原因网站启用了反爬 JS 渲染如 Vue 动态注入requests.get()获取的是初始 HTML无xxym类class值含空格或动态后缀如classxxym js-loadedclassxxym严格匹配失败。解决用contains(class, xxym)替代classxxym检查res.text是否含script标签若有且关键内容在 JS 中则需切换为selenium或playwright。4.3 Word 导出后中文乱码不是字体问题而是文档编码未声明现象.docx中标题显示为“????”但控制台打印正常原因python-docx本身不处理编码乱码源于article字符串在解析时已损坏如res.text用错编码解码解决统一用res.content.decode(utf-8, errorsignore)获取字符串errorsignore丢弃非法字节验证print(repr(article[:50]))查看是否含\ufffdUnicode 替换字符。4.4 附件链接 404不是拼接逻辑错而是网站启用了 Referer 校验现象requests.get(appendix)返回 403但浏览器直接访问正常原因服务器检查Referer请求头要求必须来自pnr.sz.gov.cn解决# 下载附件时添加 Referer attach_res requests.get(appendix, headers{**headers, Referer: res.url})4.5 进程池卡死不是代码错而是 MongoDB 连接被多进程共享现象pool.map(page_scratch, links)运行到一半停止无报错原因pymongo.MongoClient实例不能跨进程共享子进程中mycol指向同一连接触发连接池竞争解决在page_scratch()函数内重建连接def page_scratch(link): client pymongo.MongoClient(localhost, 27017) # 每进程独立实例 db client[webpages] col db[gov.publicity] # ... 插入逻辑 client.close() # 关键释放连接5. 进阶验证用 MongoDB 聚合管道校验爬取完整性及 Word 导出质量自动化检测5.1 用聚合管道验证“爬取页数 vs 实际入库数”是否一致爬虫最怕漏页。项目正文用range(1,34)爬34页但网络抖动可能导致某页get_link()返回空列表。用 MongoDB 聚合校验# 统计每页索引对应的公示数量假设URL含 /index_{}.html 或 /index.html pipeline [ { $addFields: { page_num: { $cond: { if: {$regexMatch: {input: $meta.url, regex: r/index_(\d)\.html}}, then: {$toInt: {$arrayElemAt: [{$regexFindAll: {input: $meta.url, regex: r/index_(\d)\.html}}, 0]}}, else: {$cond: {if: {$regexMatch: {input: $meta.url, regex: r/index\.html}}, then: 0, else: -1}} } } } }, {$group: {_id: $page_num, count: {$sum: 1}}}, {$sort: {_id: 1}} ] result list(mycol.aggregate(pipeline)) # 输出[{_id: 0, count: 25}, {_id: 1, count: 28}, ...] 表示第0页首页爬到25条第1页28条执行逻辑$regexFindAll提取 URL 中的页码数字$cond处理首页无数字和无效页-1$group按页码分组计数若某_id缺失如应有_id: 33但无说明该页完全漏爬。5.2 Word 导出质量自动化检测用 python-docx 读取并校验关键字段导出后人工抽查效率低。写一个校验脚本from docx import Document import re def validate_docx(filepath): doc Document(filepath) issues [] # 检查标题是否含“公示”关键词政务文档必备 if not any(公示 in p.text for p in doc.paragraphs if p.text.strip()): issues.append(未检测到‘公示’关键词) # 检查附件链接是否可点击超链接对象存在 has_hyperlink False for p in doc.paragraphs: for run in p.runs: if hasattr(run._element, rId): # 有超链接关系ID has_hyperlink True break if not has_hyperlink: issues.append(未找到附件超链接) # 检查日期格式是否为 YYYY-MM-DD date_pattern r\d{4}-\d{1,2}-\d{1,2} date_found False for p in doc.paragraphs: if re.search(date_pattern, p.text): date_found True break if not date_found: issues.append(未检测到标准日期格式) return issues # 批量校验 for doc_file in [2023-01_publicity.docx, 2023-02_publicity.docx]: errs validate_docx(doc_file) if errs: print(f{doc_file} 问题{; .join(errs)}) else: print(f{doc_file} 通过校验)校验维度语义层必须含“公示”确保文档类型正确结构层超链接对象存在证明add_hyperlink()生效格式层日期符合YYYY-MM-DD避免2023年1月1日等非机器可读格式。5.3 从那以后我每次导出 Word 前都强制走一遍“三查”流程查 MongoDB用db.gov.publicity.countDocuments({})确认总数与预期一致再用聚合管道核对分页分布查中间数据mycol.find_one({content.title: {$regex: .*公示.*}})抽样看content.body是否含乱码或截断查 Word 文件用validate_docx()脚本跑一遍输出问题列表而非只看是否报错。这三步加起来不到 10 秒却让我在交付某市政务数据归档项目时避免了因附件链接失效被退回重做的尴尬。当时客户用 Adobe Acrobat 打开 .docx 点击附件弹出 404 页面——而我的本地校验脚本早在打包前就标红了那条链接。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
Python打砖块实战:物理碰撞、子步长与实时循环调优 1. 为什么老游戏是练手的最佳题材某套入门课程讲到第 95 课的时候,突然插了一段几十年前的旧事:有个年轻人揣着一份简历去一家做街机的小公司应聘,人家问他会什么,他说“什么都会”,于是被留了下来,后来还真… · 2026/11/3 4:11:44
代码注释规范:避免多语言陷阱的技术实践 1. 项目背景与核心问题那天下午的代码评审会,整个技术部门都记得清清楚楚。新来的技术总监王总正对着投影屏幕皱眉,突然整个人僵在了那里——他面前的IDE里,密密麻麻全是西夏文注释的Java代码。会议室里鸦雀无声,只听见空调出风口… · 2026/11/3 4:10:43
测试文档编写指南:从目标定义到最佳实践 1. 项目概述"测试文章标题01"这个看似简单的标题背后,其实蕴含着丰富的可能性。作为从业多年的技术博主,我深知每个项目标题都可能是一个技术宝藏的入口。今天我们就来深度挖掘这个标题背后的技术内涵和应用场景。在实际工作中,我们… · 2026/11/3 4:10:43
NineData实现MySQL到openGauss PostgreSQL数据复制 1. 项目概述:NineData新增MySQL到openGauss PostgreSQL兼容版数据复制链路最近NineData平台上线了一个让数据库管理员们眼前一亮的新功能——支持MySQL到openGauss PostgreSQL兼容版的数据复制链路。这个功能解决了不少企业级用户在实际业务中遇到的数据库迁移难题。… · 2026/11/3 4:33:49
医院医疗设备管理系统微信小程序开发实践 1. 项目概述这个医院医疗设备管理系统微信小程序毕设项目,是我去年指导计算机专业学生完成的典型毕业设计案例。整套系统包含完整的前后端代码、数据库设计文档和技术说明文档,实现了医疗设备的全生命周期管理。从实际运行效果来看,系统日均能… · 2026/11/3 4:33:49
代码注入与Hook技术:原理、实现与应用解析 1. 代码注入与Hook技术核心概念解析在软件开发和安全研究领域,代码注入和Hook技术就像外科医生的手术刀——用得好可以治病救人,用不好则会造成严重伤害。这两种技术本质上都是通过干预程序正常执行流程来实现特定功能,但它们的实现原理和应用… · 2026/11/3 4:33:49
蒙特卡洛模拟从原理到实战:工期、库存与风险估算 上周某位做项目管理的朋友问了我一个问题:手上这个研发项目,合同里写死了六个月交付,可每个环节的工期都是估出来的,有的乐观有的悲观,最后按期完成的概率到底有多大?他手上有一张 Excel 表,每个… · 2026/11/3 4:33:49
8cm等于多少像素?厘清PPI、CSS像素与印刷分辨率的换算逻辑 做设计、写前端、跑印刷的人,几乎都被同一个问题绊过一下:8cm等于多少像素。这个问题看着像小学算术,实际上一句话根本答不完。我在带新人的时候,最常听到的两句抱怨,一句是"我按8cm做的图,打印出来矮… · 2026/11/3 4:33:49
V2X技术实现卡车自动编队行驶:原理与应用 1. 项目概述:当卡车在高速上自动排成"火车"去年夏天,我在G15沈海高速上看到一列特殊的"货运火车"——五辆重型卡车以不到10米的间距匀速行驶,头车司机偶尔调整方向盘,后车就像被无形绳索牵引般同步跟随。这正… · 2026/11/3 4:32:49
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18
STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18