VPS 搭建文件抓取系统(3)

一、为什么要做这一步

前两章完成后,已经拿到了:

  • 原始 html
  • 提取后的 txt
  • 索引文件 policy_index.csv
  • URL 去重文件 url_index.txt

但这里面并不全是“正式政策”。

同一个政策主题,经常会同时出现:

  • 正式文件
  • 记者问
  • 专家解读
  • 政策解读

例如同一个主题,可能抓到 4 条记录:

  • 《关于加强矿井水保护和利用的指导意见》
  • 《关于加强矿井水保护和利用的指导意见》记者问
  • 《关于加强矿井水保护和利用的指导意见》专家解读之一
  • 《关于加强矿井水保护和利用的指导意见》专家解读之二

如果后续要做政策研究,一般应该先把:

  • 正式文件
  • 解读类页面

区分开。

这一章只做一件事:

先生成一个“清洗后的索引文件”,把正式政策和非正式政策区分开。

注意:

这一章不会删除任何原始数据。
只是在原始索引基础上,额外生成一个新的文件。


二、先查看原始索引文件

执行:

head /srv/policybot/data/ndrc/index/policy_index.csv

你会看到类似:

关于加强投资项目在线审批监管平台和工程建设项目审批管理系统数据共享的通知,2026-02-13,https://www.ndrc.gov.cn/...,2026-02-13_xxxxx.html,2026-02-13_xxxxx.txt

每一行字段顺序是:

标题,日期,原始链接,html文件名,txt文件名

后面清洗就是根据“标题”来判断它属于哪一类。


三、创建导出目录

先创建清洗结果要放的位置。

执行:

mkdir -p /srv/policybot/export/ndrc

后面会把清洗结果放在:

/srv/policybot/export/ndrc

四、先做最简单的标题过滤版

先用标题做第一轮过滤。

规则很简单:

需要排除的标题关键词

  • 专家解读
  • 解读
  • 记者问
  • 答记者问
  • 政策解读

暂时保留的标题类型

  • 通知
  • 意见
  • 办法
  • 规定
  • 方案
  • 规划
  • 实施意见
  • 管理办法
  • 若干措施
  • 指导意见
  • 公告
  • 通报

先创建一个清洗脚本:

cat > /srv/policybot/crawler/filter_ndrc.py <<'PY'
import csv
import os

INPUT_FILE = "/srv/policybot/data/ndrc/index/policy_index.csv"
OUTPUT_ALL = "/srv/policybot/export/ndrc/policy_with_flags.csv"
OUTPUT_FORMAL = "/srv/policybot/export/ndrc/policy_formal_only.csv"
OUTPUT_NONFORMAL = "/srv/policybot/export/ndrc/policy_nonformal.csv"

exclude_keywords = [
    "专家解读",
    "记者问",
    "答记者问",
    "政策解读",
]

formal_keywords = [
    "通知",
    "意见",
    "办法",
    "规定",
    "方案",
    "规划",
    "实施意见",
    "管理办法",
    "若干措施",
    "指导意见",
    "公告",
    "通报",
]

def classify_title(title):
    title = title.strip()

    for kw in exclude_keywords:
        if kw in title:
            return "nonformal"

    for kw in formal_keywords:
        if kw in title:
            return "formal"

    return "uncertain"

with open(INPUT_FILE, "r", encoding="utf-8") as f:
    reader = csv.reader(f)
    rows = list(reader)

with open(OUTPUT_ALL, "w", newline="", encoding="utf-8") as f_all, \
     open(OUTPUT_FORMAL, "w", newline="", encoding="utf-8") as f_formal, \
     open(OUTPUT_NONFORMAL, "w", newline="", encoding="utf-8") as f_nonformal:

    writer_all = csv.writer(f_all)
    writer_formal = csv.writer(f_formal)
    writer_nonformal = csv.writer(f_nonformal)

    writer_all.writerow(["title", "date", "url", "html_file", "txt_file", "type"])
    writer_formal.writerow(["title", "date", "url", "html_file", "txt_file", "type"])
    writer_nonformal.writerow(["title", "date", "url", "html_file", "txt_file", "type"])

    formal_count = 0
    nonformal_count = 0
    uncertain_count = 0

    for row in rows:
        if len(row) < 5:
            continue

        title, date, url, html_file, txt_file = row[:5]
        record_type = classify_title(title)

        new_row = [title, date, url, html_file, txt_file, record_type]
        writer_all.writerow(new_row)

        if record_type == "formal":
            writer_formal.writerow(new_row)
            formal_count += 1
        elif record_type == "nonformal":
            writer_nonformal.writerow(new_row)
            nonformal_count += 1
        else:
            uncertain_count += 1

print("formal:", formal_count)
print("nonformal:", nonformal_count)
print("uncertain:", uncertain_count)
PY

五、运行清洗脚本

执行:

cd /srv/policybot/crawler
python filter_ndrc.py

正常会看到类似:

formal: 4xx
nonformal: 5x
uncertain: x

这三个数字的含义:

  • formal:判断为正式政策
  • nonformal:判断为解读、记者问等
  • uncertain:标题不够明显,暂时无法直接归类

六、查看清洗结果文件

执行:

ls /srv/policybot/export/ndrc

正常输出:

policy_formal_only.csv
policy_nonformal.csv
policy_with_flags.csv

七、查看带标记的总文件

执行:

head /srv/policybot/export/ndrc/policy_with_flags.csv

正常会看到类似:

title,date,url,html_file,txt_file,type
关于加强投资项目在线审批监管平台和工程建设项目审批管理系统数据共享的通知,2026-02-13,https://...,2026-02-13_xxxxx.html,2026-02-13_xxxxx.txt,formal
《关于加强矿井水保护和利用的指导意见》专家解读之一,2024-03-18,https://...,2024-03-18_xxxxx.html,2024-03-18_xxxxx.txt,nonformal

这里新增了一列:

type

用来表示这条记录的分类。


八、查看正式政策文件

执行:

head /srv/policybot/export/ndrc/policy_formal_only.csv

正常会看到标题以:

  • 通知
  • 意见
  • 办法
  • 指导意见
  • 公告

为主。

这就是后续做研究时最先会用到的数据。


九、查看非正式文件

执行:

head /srv/policybot/export/ndrc/policy_nonformal.csv

会看到类似:

  • 专家解读
  • 记者问
  • 答记者问
  • 政策解读

这些通常不是你做正式政策计量时的核心样本。


十、查看还有多少“无法判断”的记录

因为 uncertain 并没有单独导出,所以先从总文件里查。

执行:

grep ",uncertain$" /srv/policybot/export/ndrc/policy_with_flags.csv | head

如果有输出,说明有些标题不够明显。
例如可能出现:

  • 关于印发……
  • 关于进一步……
  • 实施方案
  • 若干安排

这些标题有时并不明显,但内容可能仍然是正式政策。

所以后续一般做法是:

  1. 先保留 formal
  2. 再人工看 uncertain
  3. 再决定要不要把一部分 uncertain 加进正式样本

十一、单独导出 uncertain 文件

为了后续手动检查,建议再加一个文件。

执行:

cat > /srv/policybot/crawler/filter_ndrc.py <<'PY'
import csv
import os

INPUT_FILE = "/srv/policybot/data/ndrc/index/policy_index.csv"
OUTPUT_ALL = "/srv/policybot/export/ndrc/policy_with_flags.csv"
OUTPUT_FORMAL = "/srv/policybot/export/ndrc/policy_formal_only.csv"
OUTPUT_NONFORMAL = "/srv/policybot/export/ndrc/policy_nonformal.csv"
OUTPUT_UNCERTAIN = "/srv/policybot/export/ndrc/policy_uncertain.csv"

exclude_keywords = [
    "专家解读",
    "记者问",
    "答记者问",
    "政策解读",
]

formal_keywords = [
    "通知",
    "意见",
    "办法",
    "规定",
    "方案",
    "规划",
    "实施意见",
    "管理办法",
    "若干措施",
    "指导意见",
    "公告",
    "通报",
]

def classify_title(title):
    title = title.strip()

    for kw in exclude_keywords:
        if kw in title:
            return "nonformal"

    for kw in formal_keywords:
        if kw in title:
            return "formal"

    return "uncertain"

with open(INPUT_FILE, "r", encoding="utf-8") as f:
    reader = csv.reader(f)
    rows = list(reader)

with open(OUTPUT_ALL, "w", newline="", encoding="utf-8") as f_all, \
     open(OUTPUT_FORMAL, "w", newline="", encoding="utf-8") as f_formal, \
     open(OUTPUT_NONFORMAL, "w", newline="", encoding="utf-8") as f_nonformal, \
     open(OUTPUT_UNCERTAIN, "w", newline="", encoding="utf-8") as f_uncertain:

    writer_all = csv.writer(f_all)
    writer_formal = csv.writer(f_formal)
    writer_nonformal = csv.writer(f_nonformal)
    writer_uncertain = csv.writer(f_uncertain)

    header = ["title", "date", "url", "html_file", "txt_file", "type"]

    writer_all.writerow(header)
    writer_formal.writerow(header)
    writer_nonformal.writerow(header)
    writer_uncertain.writerow(header)

    formal_count = 0
    nonformal_count = 0
    uncertain_count = 0

    for row in rows:
        if len(row) < 5:
            continue

        title, date, url, html_file, txt_file = row[:5]
        record_type = classify_title(title)

        new_row = [title, date, url, html_file, txt_file, record_type]
        writer_all.writerow(new_row)

        if record_type == "formal":
            writer_formal.writerow(new_row)
            formal_count += 1
        elif record_type == "nonformal":
            writer_nonformal.writerow(new_row)
            nonformal_count += 1
        else:
            writer_uncertain.writerow(new_row)
            uncertain_count += 1

print("formal:", formal_count)
print("nonformal:", nonformal_count)
print("uncertain:", uncertain_count)
PY

重新运行:

cd /srv/policybot/crawler
python filter_ndrc.py

查看:

head /srv/policybot/export/ndrc/policy_uncertain.csv

十二、查看某个主题的全部记录

例如查“矿井水保护和利用”:

grep "矿井水保护和利用" /srv/policybot/export/ndrc/policy_with_flags.csv

正常会看到同一主题下的多条记录,例如:

  • 正文 → formal
  • 记者问 → nonformal
  • 专家解读 → nonformal

这样就能快速看出分类是否合理。


十三、这一章的作用

完成这一章后,你会得到四个文件:

/srv/policybot/export/ndrc/policy_with_flags.csv
/srv/policybot/export/ndrc/policy_formal_only.csv
/srv/policybot/export/ndrc/policy_nonformal.csv
/srv/policybot/export/ndrc/policy_uncertain.csv

它们分别表示:

  • policy_with_flags.csv:所有记录,附带分类标记
  • policy_formal_only.csv:只保留正式政策
  • policy_nonformal.csv:解读、记者问等
  • policy_uncertain.csv:暂时无法直接判断的记录

这样以后:

  • 做研究时优先用 policy_formal_only.csv
  • 想补充样本时再看 policy_uncertain.csv
  • 想研究政策传播或舆论解释时可以看 policy_nonformal.csv

十四、这一章结束后不要做的事

当前只完成了标题级别的粗筛。
先不要急着:

  • 删除原始数据
  • 删除非正式文件
  • 覆盖原始索引
  • 直接把 uncertain 扔掉

现在最合理的做法是:

保留全部原始数据,只额外生成清洗结果。

这样以后规则要改,随时能重跑。


十五、完成状态

完成后,项目目录中新增:

/srv/policybot/export/ndrc
├── policy_formal_only.csv
├── policy_nonformal.csv
├── policy_uncertain.csv
└── policy_with_flags.csv

到这里,就完成正式政策与非正式页面的第一轮区分