ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

百度新闻评论采集实战:从搜索链接到评论接口的完整爬虫链路

百度新闻评论采集实战:从搜索链接到评论接口的完整爬虫链路 做舆情分析时最耗时间的往往不是数据建模而是数据采集。尤其是评论内容分散在不同平台、不同页面结构里找一个通用方案很难。我这次做的是百度新闻的评论内容抓取目标很明确输入一个关键词把百度新闻搜索结果下相关文章的评论批量拉下来存成结构化数据方便后续做情感分析、热点追踪。这套方案不算复杂核心链路是“搜索新闻 - 拿到新闻详情页 - 定位评论接口 - 解析评论JSON - 落库”。但真正动手做就会发现链路里每一个环节都有不少细节百度搜索结果的链接是跳转链接不能直接请求新闻详情页里的评论配置需要从页面脚本里提取评论接口的参数每个站点还不完全一样。这篇文章把完整的实现过程、踩过的坑和排查思路都写出来适合正在做爬虫项目、或者需要批量采集评论数据做分析的读者参考。1. 项目拆解先想清楚抓什么、怎么抓1.1 百度新闻的数据流转路径要抓评论不能一上来就写代码先把百度新闻这套数据流转路径摸清楚。我按照实际浏览器的访问过程拆了一遍用户打开百度新闻搜索页输入关键词搜索页返回一批新闻标题和摘要。每个新闻标题点进去其实先经过一个百度自己的跳转链接也就是URL里带/link?url的那种地址。这个跳转链接会经过302重定向最终落到新闻的源站页面可能是百家号也可能是新浪、网易、腾讯等合作媒体。到达新闻详情页之后页面里的评论组件会向百度评论接口发起异步请求拿到一串JSON数据浏览器再渲染成评论列表。所以咱们要抓的数据链路非常清晰搜索关键词 - 百度新闻搜索页HTML - 新闻跳转链接 - 新闻详情页HTML - 评论配置信息 - 评论接口JSON - 评论数据落库理解这条链路最大的价值在于你能明确每一步需要什么、输出什么以及在哪里抓数据。搜索页给的是新闻候选列表详情页给的是评论接口的“钥匙”评论接口才是真正返回评论正文的地方。很多人一上来就去搜“评论接口怎么调”但其实你得先能从详情页里找到那串接口参数否则拿到接口地址也没法用。1.2 技术选型requests直连还是自动化浏览器实现评论抓取最常见的选择有两个方向一是Python requests直接模拟HTTP请求二是Playwright或Selenium控制真实浏览器去操作页面。我这次选择了requests为主、Playwright兜底的组合方案。原因很现实评论数据本身是通过JSON接口返回的只要能用开发者工具找到这个接口requests直接请求效率最高速度最快也最好控制请求频率。唯一的麻烦是有些新闻源站的页面是纯前端渲染的评论配置信息不直接出现在HTML源码里这时候用requests抓详情页拿不到关键参数才需要上Playwright。这两个方案各有利弊我用表格整理一下方便你做选型维度requests直连Playwright/Selenium速度快毫秒级响应慢需要启动浏览器并加载渲染资源占用低一个进程就能跑高每个浏览器实例都很吃内存可控性需要手动分析接口和参数可以直接操作页面元素所见即所得反爬应对对请求头、频率更敏感更接近真实用户但同样可能被检测维护成本接口参数变了要重新抓包页面结构变了要改选择器适用场景接口明确、数据结构稳定的目标动态渲染、接口不直接暴露的目标实际项目里我建议优先用requests把整条链路跑通遇到页面动态渲染导致拿不到参数的情况再针对那一个步骤引入Playwright不要一开始就用浏览器自动化否则采集几万条评论时性能会很难看。1.3 前置准备环境与依赖项目使用Python 3.9以上版本主要依赖这几个库requests发送HTTP请求解析搜索页和调用评论接口pandas数据清洗和存储CSV用习惯了确实方便python-dotenv管理配置项比如请求间隔、关键词列表json、re、time标准库处理数据结构、正则提取和限速安装命令很简单pip install requests pandas python-dotenv如果你要处理动态页面再加一个Playwrightpip install playwright playwright install chromium开发调试阶段强烈推荐配合Charles或浏览器开发者工具使用。尤其是浏览器开发者工具的Network面板你要找评论接口直接在Network里过滤关键词comment或者api很快就能定位到真实的请求地址和参数结构。这一步是整个项目里最关键的技能毕竟接口参数不可能永远不变掌握了分析方法以后遇到任何站点的评论接口都能举一反三。2. 核心流程从搜索关键词到评论落库2.1 第一步搜索接口返回新闻列表百度新闻的搜索页地址格式是https://www.baidu.com/s?tnnewsword关键词这里tnnews是必带参数决定返回的是新闻搜索结果而不是普通网页搜索结果。请求时加上常规的User-Agent百度搜索页基本不会拦。请求返回的是HTML页面里面每一个新闻结果对应一个跳转链接。这类链接长这样https://www.baidu.com/link?urlxxx直接用正则从HTML里提取所有百度跳转链接即可。我在项目里用的提取逻辑是找到所有href属性过滤出包含/link?url的链接再结合标题附近的一段HTML区域做一一对应避免把不相关的链接混进来。这一步有两个要点。第一不要试图直接请求搜索结果里的真实新闻URL因为你用正则看到的 href 可能是相对地址或者跳转地址直接请求大概率拿不到正文页面。第二提取之后需要做一次跳转解析也就是请求一次这个跳转链接让requests跟随302拿到最终的真实URL。后续步骤都需要这个真实地址。2.2 第二步从新闻页提取评论入口参数拿到新闻详情页的真实URL后下一步是请求这个页面并从页面源码里提取评论接口所需的参数。这一步是整个项目里最容易翻车的地方。百度新闻的评论接口通常需要几个关键参数比如newsid、parent_newsid、channel等等这些参数的值不在接口地址里写死而是由新闻页面通过JavaScript渲染到全局变量中的。你需要从HTML源码里找到类似这样的片段script window.newsId xxx; window.commentConfig {parent_newsid: yyy, channel: zzz}; /script不同的新闻源站全局变量名不太一样。我在百家号源站上见过window.newsId在一些第三方站点上见过window.comment_id或window.appid。你需要先手动打开几篇新闻页面在开发者工具里查看Network面板找到评论接口请求再从它的查询参数反推页面源码中对应的全局变量。这个反推过程不需要任何破解技术纯粹就是前端调试的基本功。提取方式我建议用正则因为这类变量大多是散落在script标签里的单一赋值语句正则处理最直接。比如news_id re.search(rwindow\.newsId\s*\s*([^]), html).group(1)如果页面结构复杂也可以用BeautifulSoup先筛出所有script标签再逐段匹配逻辑上更稳但速度会稍慢。2.3 第三步调用评论接口并处理分页评论接口的地址和参数格式以你从Network面板里看到的为准。当前我实际使用中见到的主要结构是这样的请求地址https://comment.api.news.baidu.com/v1/comment/list请求参数newsid: xxx parent_newsid: yyy page: 1 size: 20返回的是一个JSON对象评论数据通常被包在一个数组里结构大致是{ data: { comments: [ { content: 这条评论的内容, like_count: 12, time: 1699999999, user: { name: 用户昵称 } } ], has_more: true } }重点看has_more或者page字段如果值为true就继续请求下一页。有些接口用last参数代替页码翻页时要把上一页返回的游标值带过来。这个字段名因接口版本而异建议你解析一次返回JSON把字段结构打印出来再写分页逻辑。调用评论接口时请求头里一定要带上Referer值就是当前新闻详情页的URL。很多情况下不带Referer接口会直接返回空列表或者错误码这是我做这个项目时踩过的最大一个坑。2.4 第四步数据清洗与存储评论数据拿回来之后不能直接存先做一轮清洗。评论JSON字段里常见的脏数据有多余的空格和换行、时间戳格式不统一、用户名为空、内容为纯表情或广告等。我一般会做这几步清洗把时间戳统一转成YYYY-MM-DD HH:MM:SS格式去除内容首尾空白字符过滤掉内容长度为0的无效评论对用户名和评论内容做简单的隐私脱敏特别是涉及手机号、邮箱等敏感信息的用正则替换一部分字符存储格式按用途选择。如果后续要进数据库存CSV就足够如果要做流式处理存JSON Lines更适合。为了兼顾两者我习惯把数据同时存成CSV和JSONL两份df.to_csv(comments.csv, indexFalse, encodingutf-8-sig) df.to_json(comments.jsonl, orientrecords, linesTrue, force_asciiFalse)utf-8-sig编码主要是为了Excel打开CSV不乱码这个细节很多新手不知道经常存完才发现中文全是乱码。3. 完整实战一套可运行的抓取脚本3.1 环境准备与依赖建议在虚拟环境里运行整个项目依赖隔离能省掉很多版本冲突的麻烦python -m venv .venv source .venv/bin/activate pip install requests pandas python-dotenv项目目录结构推荐这样组织baidu_news_comment/ ├── config.py # 全局配置关键词、请求间隔、过滤规则 ├── collector.py # 抓取核心逻辑 ├── storage.py # 数据清洗与存储 └── run.py # 入口脚本3.2 核心代码模块先看入口脚本run.py它负责串联整个流程# run.py import time import random from collector import search_news, resolve_url, fetch_comment_config, fetch_comments from storage import clean_comments, save_comments KEYWORD 人工智能 def main(): # 1. 搜索新闻 news_links search_news(KEYWORD, max_pages3) print(f共找到 {len(news_links)} 条新闻) all_comments [] for news_url in news_links: try: # 2. 解析跳转链接拿到真实新闻页地址 real_url resolve_url(news_url) # 3. 提取评论接口参数 news_id, parent_news_id fetch_comment_config(real_url) # 4. 拉取评论 comments fetch_comments(news_id, parent_news_id, real_url, max_pages5) all_comments.extend(comments) print(f{real_url}: 获取 {len(comments)} 条评论) except Exception as e: print(f处理 {news_url} 失败: {e}) # 随机休眠控制请求频率 time.sleep(random.uniform(1, 3)) # 5. 清洗和存储 df clean_comments(all_comments) save_comments(df, keywordKEYWORD) if __name__ __main__: main()搜索模块collector.py里重点是search_news函数。它请求百度新闻搜索页用正则提取跳转链接# collector.py import requests import re HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } def search_news(keyword, max_pages1): links [] for page in range(1, max_pages 1): url https://www.baidu.com/s params {tn: news, word: keyword, pn: (page - 1) * 10} resp requests.get(url, paramsparams, headersHEADERS, timeout10) resp.raise_for_status() page_links re.findall(rhref(https://www\.baidu\.com/link\?url[^]), resp.text) links.extend(page_links) time.sleep(1) return links这里的分页参数pn和普通搜索一致第1页是0第2页是10每页10条。实际测试中百度对搜索页的请求频率比较敏感连续翻页建议加1到2秒的间隔。解析跳转链接和提取评论配置的函数def resolve_url(short_url): resp requests.get(short_url, headersHEADERS, allow_redirectsTrue, timeout10) return resp.url def fetch_comment_config(news_url): resp requests.get(news_url, headersHEADERS, timeout10) html resp.text # 优先匹配 window.newsId news_id re.search(rwindow\.newsId\s*\s*([^]), html) # 有些页面是 comment_id if not news_id: news_id re.search(rcomment_id\s*:\s*([^]), html) if not news_id: raise ValueError(未找到 newsId 参数需要人工确认页面结构) parent_news_id re.search(rwindow\.parentNewsId\s*\s*([^]), html) if not parent_news_id: parent_news_id news_id # 部分页面两者相同 return news_id.group(1), parent_news_id.group(1) if parent_news_id else news_id.group(1)评论拉取函数注意带上Referer并处理分页def fetch_comments(news_id, parent_news_id, referer, max_pages5): comments [] session requests.Session() session.headers.update(HEADERS) for page in range(1, max_pages 1): api_url https://comment.api.news.baidu.com/v1/comment/list params { newsid: news_id, parent_newsid: parent_news_id, page: page, size: 20, } resp session.get(api_url, paramsparams, headers{Referer: referer}, timeout10) data resp.json() items data.get(data, {}).get(comments, []) if not items: break comments.extend(items) # 判断是否还有下一页 if not data.get(data, {}).get(has_more, False): break return comments3.3 运行与调试记录我在测试环境跑了一次关键词“人工智能”抓了3页搜索结果、约20篇新闻最后拿到300多条评论。输出效果大概是共找到 30 条新闻 https://baijiahao.baidu.com/xxx: 获取 15 条评论 https://baijiahao.baidu.com/yyy: 获取 0 条评论 ... 清洗后有效评论 286 条已保存至 comments_人工智能.csv这里有个现象很有意思20篇新闻里大概有3到4篇的评论接口返回为空。排查后发现有两种情况一是部分第三方新闻源没有接入百度评论组件自然没有评论数据二是页面做了懒加载需要滚动或点击才会触发评论请求这类页面用requests直接拿不到参数只能用Playwright处理。所以如果你遇到某篇文章评论数一直是0先别急着怀疑代码先手动打开那篇新闻看评论区是否正常加载确认页面本身有评论再回来查代码。4. 常见问题与排查技巧实录4.1 评论接口返回空列表这是出现频率最高的问题。我排查了一圈90%的原因是请求头里没有带Referer或者Referer和实际新闻页URL不一致。百度评论接口会校验这个字段缺失或者不匹配都会直接返回空数据。解决办法是在调用评论接口时手动设置session.get(api_url, headers{Referer: news_url})另外还要确认newsid和parent_newsid是否真的从当前新闻页提取到了正确的值。有些页面里这两个参数在多个位置出现过正则取到的可能是空字符串。4.2 新闻链接跳转后丢失搜索页拿到的百度跳转链接resolve之后有时候会变成https://www.baidu.com/s?wordxxxsaxxx这类地址而不是真正的新闻源站地址。这是因为百度对部分链接做了二次跳转或者页面本身触发了安全验证。我建议处理跳转时不仅依赖resp.url还要判断最终URL里是否包含具体的新闻站点域名。如果发现resolve结果还是百度域名就放弃这篇新闻继续处理下一篇不要在这个问题上死磕。4.3 请求频率过高导致结果异常表现特征是前几个请求正常到后面搜索页返回200但是找不到任何链接或者评论接口开始返回重复数据。这通常是触发频率限制的典型信号。我的处理方案是给所有请求加上随机延迟import random import time time.sleep(random.uniform(1, 3))搜索页和新闻详情页的请求间隔建议在1到2秒以上评论接口可以稍微快一点但最好也别低于0.5秒。如果累计请求量很大还可以做一个简单的token桶限流让请求速率稳定在一个固定值。4.4 新闻页面动态渲染导致正则抓不到如果你用requests请求新闻详情页发现HTML源码里根本没有newsId之类的变量说明这个页面的评论配置是通过JavaScript动态加载的。这时候正则方案直接失效两个解决办法第一用Playwright打开新闻页等待评论区域加载完成再从页面上下文里提取全局变量。Playwright里有一个很实用的APIfrom playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch(headlessTrue) page browser.new_page() page.goto(news_url) page.wait_for_selector(script) result page.evaluate(window.newsId) browser.close()第二直接在Network面板里找评论接口的真实请求地址看看它是否完整暴露了所有参数如果能拿到就不需要解析详情页直接动态拼接参数请求即可。为了便于查阅我把上面这些高频问题整理成一张速查表问题现象可能原因处理办法评论接口返回空列表缺少Referer或参数不匹配补全请求头核对newsid和parent_newsid跳转后拿到的还是百度页面链接二次跳转或安全验证放弃该条新闻继续下一条请求一段时间后全部失败请求频率过高触发限制增加随机休眠降低并发页面源码没有评论参数动态渲染改用Playwright提取或直接观察网络请求CSV打开中文乱码编码用了默认utf-8保存时使用utf-8-sig5. 合规与伦理爬虫的边界这个话题很多人不爱聊但做数据采集真的必须搞清楚。我写爬虫采集评论核心前提是只抓公开可见的数据不碰用户非公开信息不做撞库、不做批量注册、不做任何破坏性操作。具体到百度新闻评论这个场景有三条边界我一直在遵守第一遵守目标网站的robots协议和用户协议。抓取前先看https://www.baidu.com/robots.txt如果明确不允许访问某些路径就不要去碰。百度新闻搜索页和评论接口是否允许自动化访问以实际协议为准我的做法是仅用于个人学习和研究不对外提供抓取接口也不批量下载用户敏感信息。第二严格控制请求频率避免对目标服务器造成压力。抓取本身是“读取公开数据”的动作但如果频率过高就会退化成对服务的干扰。我一般在请求之间加入随机延迟并且每次只跑少量关键词跑完就停。第三数据处理时做必要的脱敏。评论内容里经常夹杂手机号、微信号、邮箱等个人信息即使公开发布也不应该被二次扩散。我的清洗逻辑里会把这些信息用正则替换成***降低数据泄露风险。如果你做这个项目的目的是发表论文或做商业分析建议再用法律视角审视一遍数据来源、数据量和用途。我这里不展开讲法律条款只说一句技术本身是中性的但使用技术的方式决定了它的性质。控制好边界爬虫项目才能真正落地。我在实际使用中还有一个体会评论抓取只是数据链路的第一环真正的价值在于抓完之后的清洗、去重、情感分析和话题聚类。这个脚本跑完生成的CSV我通常会导入到数据分析流程里按时间维度做情感趋势图能很直观地看出某个话题下公众情绪的演变过程。后续还可以在这个框架上扩展出“多关键词监控”“定时增量采集”“评论回复链抓取”等功能整个系统就能从一次性脚本升级为一个轻量级的舆情数据平台。
返回列表