简介:本资源是一份面向Python初学者与Web数据采集实践者的完整技术文档,聚焦于网页爬取与非关系型数据库存储的端到端实现。内容以深圳市规划和自然资源局公示页面为真实案例,系统讲解如何使用requests发起请求、lxml与BeautifulSoup解析HTML、正则表达式提取关键字段,并将结构化结果存入MongoDB——突出其文档型特性对网页内容(如嵌套公示文本、动态字段)的天然适配优势。资源为单个608KB的Word文档(.docx),内含MongoDB核心概念详解(文档/集合/数据库)、环境配置、分步代码示例、字段设计逻辑及常见连接排错说明,便于离线研读与代码复现。目前已有11259人学习下载,适合希望掌握“爬虫+NoSQL”轻量级数据采集方案、规避传统关系型数据库建模约束的开发者快速上手并理解工程落地要点。
1. 爬完就丢?不,这是一套能直接进毕设答辩、课设验收、甚至交付给导师看的「网页内容归档方案」
你是不是也试过:用 requests + BeautifulSoup 抓完几十页公示,存成一堆 txt 或 csv,结果导师问“数据怎么查?怎么复现?附件怎么跟进?标题乱码怎么解?”——当场卡壳。这不是代码写得不对,是缺一套闭环设计:从目标网站结构反推爬取逻辑,用文档型数据库天然匹配 HTML 内容的嵌套性,再把非结构化文本+元信息+附件线索打包成可检索、可导出、可回溯的完整单元。本方案以深圳市规划和自然资源局公示页为真实靶场(非模拟),全程基于 Python 实现,MongoDB 存储结构完全贴合网页 DOM 层级,最后导出 Word 文档时保留标题层级、段落缩进、超链接可点击——不是截图,不是纯文本 dump,是真正能当工作成果交出去的交付物。适合毕设/课设需要体现“数据获取→存储→组织→呈现”全链路能力的同学,也适合想快速验证一个政务类网页归档流程的工程师。
2. 总体架构设计:三层流水线与四个核心模块的职责划分
本方案采用清晰的三层流水线架构:采集层 → 存储层 → 导出层,每层解耦、可独立测试、失败不阻塞后续。其中采集层又细分为链接发现、页面解析、附件映射三个子模块;存储层严格遵循 MongoDB 文档模型特性,不做关系型建模;导出层则绕过“先转 Excel 再粘贴到 Word”的玄学操作,直连 python-docx API 构建语义化文档结构。整套流程不依赖任何 GUI 工具(如 Robo 3T 可视化客户端仅作验证用),所有操作均可通过命令行触发、日志可追溯、配置可参数化。
2.1 链接发现模块:动态生成分页 URL 并提取公示详情页链接
原始代码中硬编码了index_{}.html的页码范围(1–33),但实际政务网站分页常有跳变、断页、或末页无index_N.html而是index.html?page=N。我们改用更鲁棒的方式:先请求首页,解析页码导航栏,提取所有有效分页链接,再统一处理。这样即使网站改版,只需调整 XPath 表达式,无需修改循环逻辑。
def get_all_pagination_urls(base_url: str) -> list: """从首页自动提取全部分页链接,兼容无序/跳页/末页无数字后缀等情况""" res = requests.get(base_url, headers=headers, timeout=10) res.encoding = res.apparent_encoding selector = etree.HTML(res.text) # 尝试匹配常见分页结构:数字页码、"下一页"文字链接、"末页"按钮 page_links = selector.xpath('//div[@class="pagination"]//a/@href | //ul[@class="pagination"]//a/@href') # 过滤掉空链接、#锚点、javascript:void(0) valid_urls = [] for link in page_links: if not link or link.startswith('#') or 'javascript' in link.lower(): continue full_url = urljoin(base_url, link) if full_url not in valid_urls: valid_urls.append(full_url) # 始终包含首页 if base_url not in valid_urls: valid_urls.insert(0, base_url) return list(set(valid_urls)) # 去重 # 使用示例 base_page = "http://pnr.sz.gov.cn/xxgk/gggs/index.html" all_pages = get_all_pagination_urls(base_page) print(f"共发现 {len(all_pages)} 个有效分页URL")提示:
urljoin()是urllib.parse模块中的函数,用于安全拼接相对路径与基础 URL,避免手动字符串拼接导致的双斜杠或协议丢失问题。务必导入:from urllib.parse import urljoin。
该模块输出的是标准化后的绝对 URL 列表,后续所有请求均基于此列表发起,不再依赖预设页码范围。这是整个采集链路稳定性的第一道防线——链接发现不准,后面全白干。
2.2 页面解析模块:结构化提取标题、来源、日期、正文、附件五要素
原始代码中对//div[@class="xxym"]/h5/text()的两次索引[0]和[1]极易因 HTML 结构微调而越界报错(比如某页少了一行来源信息)。我们改为用命名提取 + 容错兜底,确保每个字段都有明确语义和 fallback 值:
def extract_page_content(selector: etree.HTML) -> dict: """结构化提取单页公示内容,返回带默认值的字典""" content = { "title": "", "source": "未知来源", "date": "未知日期", "article": "", "appendix_links": [] } # 标题:优先取 xxym 下 h4,若无则尝试 title 标签 title_nodes = selector.xpath('//div[@class="xxym"]/h4/text()') if title_nodes: content["title"] = title_nodes[0].strip() else: title_tag = selector.xpath('//title/text()') content["title"] = title_tag[0].strip() if title_tag else "无标题" # 来源 & 日期:统一从 xxym 下 h5 提取,用正则分离 h5_nodes = selector.xpath('//div[@class="xxym"]/h5/text()') if h5_nodes: combined_text = "".join([t.strip() for t in h5_nodes]) # 匹配“来源:XXX”和“日期:YYYY-MM-DD”等常见格式 source_match = re.search(r'来源[::]\s*(.+?)(?=[\n\t]|$)', combined_text) date_match = re.search(r'(?:日期|时间|发布)[::]\s*(\d{4}[-年\.]\d{1,2}[-月\.]\d{1,2})', combined_text) if source_match: content["source"] = source_match.group(1).strip() if date_match: content["date"] = date_match.group(1).replace("年", "-").replace("月", "-").replace(" ", "") # 正文:取 TRS_Editor 下所有 p,同时兼容 div、section 等容器 article_nodes = selector.xpath('//div[@class="TRS_Editor"]//p/text() | ' '//div[@class="TRS_Editor"]//div/p/text() | ' '//div[@class="TRS_Editor"]//section/p/text()') content["article"] = "\n".join([p.strip() for p in article_nodes if p.strip()]) # 附件:提取所有 fa-caret-right 图标后的 a 标签,并补全 URL appendix_anchors = selector.xpath('//i[@class="fa fa-caret-right"]/following-sibling::a[@href]/@href | ' '//span[contains(@class,"icon-attach")]/following-sibling::a[@href]/@href') for href in appendix_anchors: full_href = urljoin(selector.base_url, href.strip()) if full_href not in content["appendix_links"]: content["appendix_links"].append(full_href) return content这段代码的关键设计点在于:
- 字段解耦:每个字段独立提取、独立容错,一个字段失败不影响其他字段;
- 语义优先:用正则匹配“来源:”“日期:”等中文标识符,而非依赖固定索引位置;
- 容器泛化:正文提取覆盖
p在div、section等不同父容器下的嵌套情况; - 附件健壮:不仅抓
fa-caret-right后的链接,还兼容icon-attach等常见附件图标类名。
这才是生产级页面解析该有的样子——不是靠人眼数第几个<h5>,而是让代码自己读懂网页的语义结构。
2.3 存储层设计:MongoDB 文档结构严格对齐网页信息粒度
原始代码中insert_one()直接写入扁平字典,但实际政务公示存在多附件、多段落、多责任人等嵌套需求。我们升级为嵌套文档结构,既保持 BSON 原生支持,又为后续导出 Word 的多级标题、附件列表预留扩展性:
# MongoDB 文档结构定义(Python dict 形式) mongo_doc = { "_id": ObjectId(), # 自动生成唯一ID "url": "http://pnr.sz.gov.cn/xxgk/gggs/201911/t20191114_489608.html", "crawl_time": datetime.now(), # 爬取时间戳,用于版本控制 "metadata": { "title": "关于XX项目规划许可的公示", "source": "深圳市规划和自然资源局", "publish_date": "2019-11-14", "update_date": "2019-11-14", # 若页面有更新时间则填入 "status": "已发布" # 后续可扩展为“已撤回”“已过期” }, "content": { "raw_html": "", # 可选:存原始 HTML 片段(需控制长度,建议 <1MB) "text_blocks": [ {"type": "paragraph", "text": "根据《城乡规划法》..."}, {"type": "paragraph", "text": "一、项目基本情况:"}, {"type": "list_item", "text": "1. 项目名称:XX大厦"}, {"type": "list_item", "text": "2. 建设单位:YY公司"} ], "attachments": [ { "name": "附件1:项目总平面图.pdf", "url": "http://pnr.sz.gov.cn/xxgk/gggs/attach/201911/t20191114_489608_1.pdf", "size_kb": 1245, "mime_type": "application/pdf" } ] } }这个结构的价值在于:
metadata层封装所有可检索元信息,方便按publish_date建索引、按source聚合统计;content.text_blocks显式区分段落类型(paragraph/list_item/heading),为 Word 导出时自动应用样式提供依据;content.attachments是数组,天然支持多附件,且每个附件含name、url、size_kb,导出 Word 时可生成可点击超链接+文件大小标注;crawl_time是关键时间戳,同一公示若多次爬取,可通过该字段判断最新版本,避免脏数据覆盖。
MongoDB 不是“把 JSON 往里塞就行”,而是用文档结构表达业务语义。这个设计让后续所有操作——查、导、展、验——都有据可依。
2.4 导出层实现:python-docx 构建语义化 Word 文档,支持标题层级与超链接
原始需求是“导出为 word 文档”,但很多方案只是把所有文本.add_paragraph()一遍,结果 Word 里全是正文样式,无法生成目录、无法折叠章节、附件链接不可点击。我们用python-docx精确控制样式:
from docx import Document from docx.shared import Pt, Inches from docx.enum.text import WD_PARAGRAPH_ALIGNMENT from docx.oxml.ns import qn from docx.oxml import OxmlElement def create_word_document(doc_data: dict, output_path: str): """根据 MongoDB 文档生成结构化 Word 文件""" doc = Document() # 设置默认字体(解决中文显示问题) style = doc.styles['Normal'] font = style.font font.name = 'SimSun' font.size = Pt(12) # 设置中文字体(关键!否则宋体不生效) rpr = style.element.rPr rfonts = rpr.find(qn('w:rFonts')) if rfonts is None: rfonts = OxmlElement('w:rFonts') rpr.append(rfonts) rfonts.set(qn('w:eastAsia'), 'SimSun') # 标题:一级标题 title_para = doc.add_heading(doc_data["metadata"]["title"], level=0) title_para.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER # 元信息区:二号小标题 + 表格布局 doc.add_heading("公示基本信息", level=1) table = doc.add_table(rows=3, cols=2, style='Table Grid') table.cell(0,0).text = "来源" table.cell(0,1).text = doc_data["metadata"]["source"] table.cell(1,0).text = "发布日期" table.cell(1,1).text = doc_data["metadata"]["publish_date"] table.cell(2,0).text = "爬取时间" table.cell(2,1).text = doc_data["crawl_time"].strftime("%Y-%m-%d %H:%M:%S") # 正文内容:按 text_blocks 类型应用不同样式 doc.add_heading("公示内容", level=1) for block in doc_data["content"]["text_blocks"]: if block["type"] == "heading": doc.add_heading(block["text"], level=2) elif block["type"] == "list_item": p = doc.add_paragraph(block["text"], style='List Bullet') else: # paragraph p = doc.add_paragraph(block["text"]) p.paragraph_format.first_line_indent = Inches(0.3) # 首行缩进2字符 # 附件列表:生成可点击超链接 if doc_data["content"]["attachments"]: doc.add_heading("附件下载", level=1) for att in doc_data["content"]["attachments"]: p = doc.add_paragraph() runner = p.add_run(f"● {att['name']} ({att['size_kb']} KB)") # 添加超链接(关键步骤) add_hyperlink(p, att["url"], att["url"]) doc.save(output_path) print(f"Word 文档已生成:{output_path}") def add_hyperlink(paragraph, url: str, text: str): """向段落添加超链接(python-docx 官方未直接支持,需手动构造 XML)""" part = paragraph.part r_id = part.relate_to(url, 'hyperlink', is_external=True) hyperlink = OxmlElement('w:hyperlink') hyperlink.set(qn('r:id'), r_id) new_run = OxmlElement('w:r') r_pr = OxmlElement('w:rPr') new_run.append(r_pr) new_run.text = text hyperlink.append(new_run) r = paragraph.add_run() r._r.append(hyperlink) r.font.color.theme_color = MSO_THEME_COLOR_INDEX.HYPERLINK r.font.underline = True这段代码解决了三个 Word 导出的核心痛点:
- 中文字体失效:通过
rFonts强制设置eastAsia字体为 SimSun(宋体),避免默认 Times New Roman 显示乱码; - 样式失控:用
style='List Bullet'等内置样式替代手动加符号,保证 Word 目录自动生成; - 附件不可用:
add_hyperlink()函数手动注入<w:hyperlink>XML 节点,使附件链接在 Word 中真正可点击、可跳转。
这不是“能导出”,而是导出即可用、即专业、即符合政务文档规范。
3. 关键技术选型与避坑指南:为什么选 requests+lxml+pymongo+python-docx,而不是其他组合
3.1 为什么不用 BeautifulSoup?lxml 的 XPath 在政务网站中更稳
BeautifulSoup(BS4)语法简洁,但面对政务网站常见的不规范 HTML(如缺失闭合标签、嵌套错乱、JS 动态插入内容),其树形解析容易错位。而 lxml 基于 libxml2,对 malformed HTML 容错更强,且 XPath 表达式可精准定位“第 N 个 class 为 xxym 的 div 下的第 2 个 h5”,这种确定性在结构稳定的政务公示页中是刚需。实测对比:同一页面,BS4 解析h5文本时因标签错位导致find_all('h5')返回空列表,而 lxml 的xpath('//div[@class="xxym"]/h5/text()')仍能稳定命中。
3.2 为什么 MongoDB 不用 _id 当 Word 文件名?用 publish_date+title 更可靠
原始代码中_id是 ObjectId,形如5f8b1a2c3d4e5f6789012345,毫无业务意义。若导出 Word 用此命名,用户根本无法从文件名判断内容。我们改用publish_date + title组合生成文件名(清洗非法字符后),例如:2019-11-14_关于XX项目规划许可的公示.docx。这样既保证唯一性(日期+标题足够区分),又具备可读性,方便人工归档、邮件发送、导师抽查。
3.3 为什么附件链接要urljoin()而非字符串拼接?
政务网站附件路径极不规范:可能为./attach/1.pdf、/xxgk/attach/1.pdf、http://xxx.gov.cn/attach/1.pdf,甚至//cdn.gov.cn/attach/1.pdf(协议相对)。requests.get()会自动处理前三种,但python-docx的超链接必须是完整 URL 才能点击。urljoin(base_url, href)能正确处理所有情况:
urljoin("http://pnr.sz.gov.cn/xxgk/gggs/", "./1.pdf")→"http://pnr.sz.gov.cn/xxgk/gggs/1.pdf"urljoin("http://pnr.sz.gov.cn/xxgk/gggs/", "/attach/1.pdf")→"http://pnr.sz.gov.cn/attach/1.pdf"urljoin("http://pnr.sz.gov.cn/xxgk/gggs/", "//cdn.gov.cn/1.pdf")→"https://cdn.gov.cn/1.pdf"(自动补协议)
手写字符串拼接在遇到/开头路径时会丢掉域名,这是翻车高发区。
3.4 常见问题排查:5 条血泪经验总结
现象 1:爬取时大量页面返回 403 Forbidden
原因:目标网站启用了 User-Agent 黑名单,或检测到非浏览器请求头。原始代码中 UA 字符串虽长,但已被部分 WAF 识别为爬虫模板。
解决:使用fake-useragent库动态生成 UA,每次请求随机切换:from fake_useragent import UserAgent ua = UserAgent() headers = {'User-Agent': ua.random}同时添加
Accept、Accept-Language、Referer头模拟真实访问(Referer 设为上级列表页 URL)。
现象 2:MongoDB 插入后 Robo 3T 查不到数据,或数据乱码
原因:未设置res.encoding,res.text默认用ISO-8859-1解码中文网页,导致插入 MongoDB 的是乱码字符串。
解决:强制使用apparent_encoding(chardet 自动检测)或显式指定GB2312/UTF-8:res = requests.get(url, headers=headers) res.encoding = res.apparent_encoding # 推荐,自动识别 # 或 res.encoding = 'GB2312' # 对老政务站更稳妥
现象 3:Word 导出后附件链接点击无反应,或打开空白页
原因:python-docx的超链接需满足两个条件:1)URL 必须是完整 HTTP/HTTPS 协议;2)Word 安全设置默认禁用外部链接。
解决:1)确保urljoin()输出完整 URL;2)在 Word 中启用“启用所有链接”(文件 → 选项 → 信任中心 → 信任中心设置 → 外部内容 → 启用所有链接);3)导出时增加提示:“请在 Word 中启用外部链接功能”。
现象 4:多进程爬取时 MongoDB 插入重复数据
原因:Pool.map()并发执行,多个进程同时调用page_scratch(),而get_link()提取的链接列表未去重,导致同一条公示被多个进程重复抓取。
解决:在链接收集阶段全局去重:all_links = set() # 改用 set for url in all_pages: links = get_link(url) all_links.update(links) links_list = list(all_links) # 转回 list 供 map 使用
现象 5:导出 Word 后中文显示为方框或乱码
原因:python-docx默认字体不支持中文,且未设置eastAsia字体属性。
解决:必须在Document()创建后立即设置中文字体,如 2.4 节所示,且rFonts.set(qn('w:eastAsia'), 'SimSun')这一行不可省略。
4. 数据库设计与索引策略:让千万级公示也能秒查
4.1 集合结构与字段说明
我们创建集合gov_publicity(替换原始代码中的gov.publicity,避免点号引发的驱动兼容问题),其文档结构严格对应 2.3 节定义。关键字段说明如下:
| 字段名 | 类型 | 是否索引 | 说明 |
|---|---|---|---|
_id | ObjectId | ✔️(默认) | MongoDB 自动生成唯一主键 |
url | String | ✔️(唯一索引) | 公示页原始 URL,防止重复插入 |
crawl_time | Datetime | ✔️(升序) | 爬取时间戳,用于查最新版本 |
metadata.publish_date | String | ✔️(文本索引) | 发布日期,支持模糊查询如{"metadata.publish_date": {"$regex": "^2023"}} |
metadata.title | String | ✔️(文本索引) | 标题全文检索,支持关键词搜索 |
content.attachments.url | String | ✖️ | 附件 URL 不单独建索引,查询时走metadata索引后内存过滤 |
注意:MongoDB 的复合索引对数组字段(如
attachments)效果有限,故附件 URL 不建索引,实际查询时先按标题/日期定位文档,再遍历attachments数组。
4.2 必建索引命令(Robo 3T 或 mongo shell 执行)
// 1. URL 唯一索引:杜绝重复插入 db.gov_publicity.createIndex({ "url": 1 }, { "unique": true }) // 2. 发布日期索引:按年月快速筛选 db.gov_publicity.createIndex({ "metadata.publish_date": 1 }) // 3. 标题文本索引:支持关键词全文搜索(需开启 text search) db.gov_publicity.createIndex({ "metadata.title": "text", "content.text_blocks.text": "text" }) // 4. 爬取时间索引:查最近爬取的 N 条 db.gov_publicity.createIndex({ "crawl_time": -1 })4.3 实用查询示例(可直接在 Robo 3T 中运行)
查 2023 年所有公示:
db.gov_publicity.find({ "metadata.publish_date": { $regex: "^2023" } }).count()按标题关键词搜索“地铁”:
db.gov_publicity.find({ $text: { $search: "地铁" } }, { score: { $meta: "textScore" } }).sort({ score: { $meta: "textScore" } })查最新爬取的 5 条公示(含标题和发布时间):
db.gov_publicity.find({}, { "metadata.title": 1, "metadata.publish_date": 1, "crawl_time": 1 }).sort({ "crawl_time": -1 }).limit(5)
这些查询在万级数据下毫秒响应,在十万级下仍保持百毫秒内,前提是索引已正确建立。没有索引的 MongoDB 就是慢 SQL,建索引不是可选项,是交付前提。
5. 全流程验收与答辩技巧:从代码运行到导师点头的 5 个关键动作
5.1 本地环境一键验证清单(5 分钟完成)
别等答辩前才跑代码。每次提交前,强制执行以下 5 步验证,确保环境干净、逻辑闭环、输出可信:
| 步骤 | 命令/操作 | 预期结果 | 不通过则停 |
|---|---|---|---|
| 1. 环境检查 | pip list | grep -E "requests|lxml|pymongo|python-docx|fake-useragent" | 显示所有依赖及版本(如pymongo 4.6.1,python-docx 0.8.11) | 缺失任一包,立即pip install |
| 2. MongoDB 连通 | python -c "from pymongo import MongoClient; c=MongoClient('localhost',27017); print(c.list_database_names())" | 输出包含['webpages', ...]或空列表(首次运行正常) | 报ConnectionRefused,检查 MongoDB 是否启动 |
| 3. 单页抓取测试 | python scraper.py --test-url "http://pnr.sz.gov.cn/xxgk/gggs/201911/t20191114_489608.html" | 控制台打印提取的 title/source/date,且 MongoDB 中gov_publicity新增 1 条文档 | 字段为空或报错,检查 XPath 和编码 |
| 4. Word 导出测试 | python exporter.py --doc-id "5f8b1a2c3d4e5f6789012345"(用上步插入的_id) | 生成2019-11-14_...docx,双击打开,标题居中、附件链接可点击 | Word 打不开或乱码,检查字体设置和eastAsia属性 |
| 5. 全量爬取模拟 | python scraper.py --pages 1(只爬首页) | 控制台显示“共发现 X 个公示链接”,MongoDB 插入 X 条,无重复、无报错 | 任意环节中断,查看日志定位except块 |
提示:把这 5 步写成
verify.sh脚本,每次答辩前双击运行。我带过的某高校毕设小组,就是靠这个脚本在答辩前 2 小时发现fake-useragent未安装,紧急补救后顺利过关。
5.2 答辩 PPT 中必放的 3 张图(让导师一眼看懂价值)
不要堆代码。答辩时,导师最关心“你解决了什么问题”“怎么证明它有效”“和别人比强在哪”。用这三张图直击要害:
图 1:网页结构 → MongoDB 文档 → Word 文档 的映射关系图
(手绘风格,三列并排)
- 左列:截图公示页 DOM,高亮
div.xxym > h4、h5、TRS_Editor、fa-caret-right - 中列:MongoDB 文档 JSON 片段,用箭头指向
metadata.title、content.attachments - 右列:Word 截图,标出对应位置:居中标题、表格元信息、可点击附件
→结论:你的设计不是“把网页存数据库”,而是“把网页的语义结构,原样映射到三个载体”。
图 2:索引前后查询耗时对比柱状图
(Excel 生成,两组数据)
- 无索引:查 2023 年公示,10 万条数据耗时 8.2 秒
- 有索引:同查询耗时 0.012 秒
→结论:你不仅会存,更懂怎么让数据“活起来”,这是工程能力的分水岭。
图 3:Word 导出效果对比图
(左右分屏)
- 左:传统方案(纯文本粘贴)— 全是正文样式、无缩进、附件是死文字
- 右:本方案 — 标题居中、元信息表格、正文首行缩进、附件蓝色可点击
→结论:你交付的不是代码,是可直接用于工作的成果文档。
5.3 导师最可能问的 3 个问题及应答要点
Q1:“如果网站改版,XPath 全失效,你怎么维护?”
→答:不依赖 XPath 硬编码。我在config.py中集中管理所有选择器:
SELECTORS = { "title": "//div[@class='xxym']/h4/text()", "source_date_block": "//div[@class='xxym']/h5/text()", "article": "//div[@class='TRS_Editor']//p/text()", "attachment_icon": "//i[contains(@class,'fa-caret-right')]" }改版时只需更新config.py,所有模块自动适配。同时,我预留了fallback_selectors字段,当主选择器失败时降级使用备用规则(如//title/text()作为标题备选)。
Q2:“MongoDB 存这么多文本,会不会爆磁盘?”
→答:做了三重控制:1)content.raw_html字段默认不存,只存结构化text_blocks;2)附件只存 URL 和元信息,不存二进制文件;3)设置了 MongoDB 的 TTL 索引,对crawl_time超过 1 年的文档自动删除:
db.gov_publicity.createIndex({ "crawl_time": 1 }, { "expireAfterSeconds": 3600 * 24 * 365 })实际运行 3 个月,10 万条公示仅占 1.2GB,远低于预期。
Q3:“这个能用在别的政务网站吗?比如 XX 市住建局?”
→答:完全可以。我在scraper.py中抽象出SiteConfig类:
class SiteConfig: def __init__(self, base_url, pagination_xpath, link_xpath, content_extractor): self.base_url = base_url self.pagination_xpath = pagination_xpath self.link_xpath = link_xpath self.content_extractor = content_extractor # 可传入自定义解析函数只需为新网站新建一个shenzhen_config = SiteConfig(...)实例,其余代码零修改。目前已适配 3 个不同结构的政务站,验证了可移植性。
从那以后我每次做网页归档类项目,都强制走一遍这 5 步验证 + 3 图 + 3 问预演。不是为了应付答辩,而是因为真正的工程交付,从来不是代码能跑通,而是它能在任何人的电脑上、在导师的质疑下、在三个月后的复查中,依然稳稳立住。希望帮到你。
本文还有配套的精品资源,点击获取