跳转至

模块-爬虫扫描-变量引用导致爬取网址为空

问题现象

  • 站点管理页面中,"中国证券报快讯"(爬虫文件 news_zhon.py)的"爬取网址"列显示为空
  • 其他使用直接字符串常量的站点(如 crawl_caixin.py)URL 正常显示

核心问题

根因 1scanner.py_parse_crawler_meta() 仅解析 ast.Constant 类型的赋值值,不处理变量引用。

news_zhon.py 中写入形式:

target_url = _TARGET_URL  # _TARGET_URL 是模块级变量,值为字符串

AST 节点类型为 ast.Name,被 isinstance(item.value, ast.Constant) 条件过滤掉,URL 始终为空字符串存入数据库。

根因 2sync_crawlers_to_db() 只新增不更新已有记录。即使解析逻辑修复,数据库中已存在的空 URL 记录不会被覆盖。

解决方法

步骤 1:修复 AST 解析,支持变量引用

modules/web-crawler/crawlers/scanner.py

新增 _resolve_ast_value() 辅助函数,支持三种值类型: - ast.Constant:直接字符串常量("https://...") - ast.Name:变量引用(_TARGET_URL),从模块级常量表查找 - ast.JoinedStr:f-string(f"https://..."

def _resolve_ast_value(node, constants):
    if isinstance(node, ast.Constant) and isinstance(node.value, str):
        return node.value
    if isinstance(node, ast.Name) and node.id in constants:
        return constants[node.id]
    if isinstance(node, ast.JoinedStr):
        # f-string 拼接
        ...

修改前需先收集模块级常量:

top_level_constants = {}
for node in ast.iter_child_nodes(tree):
    if isinstance(node, ast.Assign):
        ...
        top_level_constants[target.id] = str(node.value.value)

步骤 2:同步时更新已有记录

sync_crawlers_to_db() 中,对已存在的记录也重新解析元数据并更新:

for code_file in code_files:
    meta = _parse_crawler_meta(code_file)
    if code_file not in db_sites:
        # 新增
        ...
    else:
        # 更新已有记录的 name 和 url
        existing = db_sites[code_file]
        if existing.name != meta["name"] or existing.url != meta["url"]:
            existing.name = meta["name"]
            existing.url = meta["url"]

步骤 3:部署

# 本地
git add modules/web-crawler/crawlers/scanner.py
git commit -m "fix: 同步时更新已有爬虫记录的 name 和 url 元数据"
git push origin main

# 服务器(后端代码变更,需重启)
ssh news "cd /www/news && git pull origin main && systemctl restart news-backend"

下次防错措施

  • 快速定位:检查 _parse_crawler_meta() 中 AST 节点类型判断是否覆盖了爬虫文件中使用的赋值形式
  • 验证命令
    ssh news "cd /www/news && venv/bin/python -c \"
    import requests
    r = requests.get('http://127.0.0.1:8000/api/web-crawler/sites')
    for s in r.json()['data']:
        if not s['url']:
            print(f'空白URL: {s[\"name\"]} (file={s[\"code_file\"]})')
    \""
    
  • 流程改进:新增爬虫文件后,在站点管理页面检查"爬取网址"是否正常显示