影评周公子 2026-04-11 17:35 采纳率: 98.8%
浏览 1
已采纳

如何用Python从零开始爬取网页并提取标题和链接?

常见技术问题: 使用 `requests` + `BeautifulSoup` 爬取网页时,代码能正常获取响应(状态码200),却始终提取不到 `<title>` 标签内容或 `<a>` 链接,返回 `None` 或空列表。根本原因常有三类:一是网页标题实际位于 `<meta property="og:title">` 或 `<meta name="title">` 中,而非标准 `<title>` 标签;二是链接被 JavaScript 动态渲染(如 SPA 应用),`requests` 无法执行 JS,导致 `soup.find_all('a')` 找不到真实链接;三是 HTML 解析器选择不当(如默认 `html.parser` 对嵌套不良标签容错差),或未指定正确编码(如网页声明 `charset=gbk` 但未设置 `response.encoding='gbk'`),造成乱码与解析失败。此外,忽略 `User-Agent` 头易触发反爬返回 403 或空白页,而未检查 `response.raise_for_status()` 则掩盖请求异常。这些问题在入门实践中高频出现,需结合开发者工具 Network 面板验证响应体真实性,并优先选用 `lxml` 解析器、显式设置编码与请求头。</title>
  • 写回答

1条回答 默认 最新

  • 马迪姐 2026-04-11 17:35
    关注
    ```html

    一、现象层:表象异常——“状态码200,却取不到title和a标签”

    这是初学者最常报错的场景:代码无语法错误,response.status_code == 200,但 soup.find('title') 返回 Nonesoup.find_all('a') 返回空列表 []。表面看是“解析失败”,实则响应体内容与预期严重偏离——可能返回了反爬中间页、JS占位符、压缩空白HTML,甚至403伪装成200的“软封锁”页面。

    二、验证层:诊断先行——用开发者工具穿透HTTP真相

    • 在浏览器中按 <kbd>F12</kbd> → 切换到 Network 面板 → 刷新页面 → 找到目标URL请求 → 点击 → 查看 Response 标签页(非Preview)
    • 对比:requests.get(url).text 输出是否与Network中Response原始内容一致?若不一致,说明存在:gzip未解压、charset误判、或服务端基于User-Agent返回差异化HTML
    • 关键动作:右键 → Copy → Copy response → 粘贴至本地文件,用BS4直接解析该文件,排除网络环节干扰

    三、根源层:三大核心缺陷分类剖析

    类别典型表现技术本质检测方式
    语义迁移<meta property="og:title"> 存在,<title>为空或为模板文本SEO/社交分享优化导致标题语义外移soup.find('meta', property='og:title')
    渲染延迟<div id="app"></div> 占满body,<a>仅存在于JS执行后DOMrequests仅获静态HTML骨架,无JS引擎执行能力Network → Disable Cache + Disable JS → 刷新,若链接消失则确认为JS渲染
    解析失真中文乱码、<li><a>嵌套错位、find()匹配失效html.parser容错弱;response.encoding未同步<meta />print(repr(response.content[:100])) 观察原始字节;print(soup.original_encoding)

    四、工程层:鲁棒性增强实践方案

    1. 请求头标准化:必须设置 'User-Agent''Accept''Accept-Language',建议复用主流浏览器指纹
    2. 编码显式声明:优先使用 response.apparent_encoding,再 fallback 到 response.headers.get('content-type') 中的 charset
    3. 解析器升级:安装 lxmlpip install lxml),构造时强制指定 BeautifulSoup(html, 'lxml') —— 其容错率比 html.parser 高3–5倍
    4. 多源标题兜底
      def get_page_title(soup):
          return (soup.title.string.strip() if soup.title and soup.title.string else None) \
              or soup.find('meta', property='og:title').get('content') if soup.find('meta', property='og:title') else None \
              or soup.find('meta', attrs={'name': 'title'}).get('content') if soup.find('meta', attrs={'name': 'title'}) else None
      

    五、架构层:超越requests+BS4的演进路径

    当动态渲染占比>30%、反爬策略升级(如WebDriver检测、行为指纹)、或需会话维持(登录态、WebSocket心跳)时,应启动架构升维:

    graph LR A[requests+BS4] -->|静态页面/轻量采集| B(成熟方案) A -->|SPA/SSR/风控页| C[Playwright/Puppeteer] C --> D[支持JS执行、截图、网络拦截、真实用户行为模拟] C --> E[可导出PDF/HTML快照供BS4二次解析] B --> F[生产环境必须添加:重试机制、代理池、Referer轮换、请求间隔控制]

    六、防御层:反模式警示与避坑清单

    • ❌ 忽略 response.raise_for_status() —— 掩盖403/429/503等非200但被伪装的响应
    • ❌ 直接用 soup.text 提取链接 —— 应始终用 tag.get('href') 避免文本污染
    • ❌ 在未验证 response.content 长度时调用BS4 —— 可能是1KB的反爬提示页
    • ✅ 建立采集健康度监控:记录每次请求的 len(response.content)soup.title 是否存在、len(soup.find_all('a')) 分布直方图
    • ✅ 对GB2312/GBK网页,强制 response.encoding = 'gbk' 后再 response.text,而非依赖自动探测

    七、高阶延伸:从爬虫到数据治理的认知跃迁

    对5年以上从业者而言,此问题已不仅是技术选型问题,更是数据可信度治理起点:需建立「HTML源可信等级」评估模型——将页面按渲染方式(SSR/CSR/SSG)、反爬强度(Header校验/JS挑战/生物特征)、结构稳定性(Schema.org标记/微数据)打标,并驱动后续解析策略路由。例如:CSR页面自动触发Playwright快照,SSR页面启用lxml增量解析,而含application/ld+json的页面优先提取结构化数据,彻底绕过DOM遍历。

    ```
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

问题事件

  • 已采纳回答 4月12日
  • 创建了问题 4月11日