使用Python 3.7的BeautifulSoup爬取WSJ文章无结果求排查
我帮你排查了代码和WSJ的爬取逻辑,发现几个关键问题导致你没拿到结果,下面一步步给你分析和修正方案:
一、为啥代码没输出?核心问题拆解
1. 反爬拦截:缺失关键请求头
WSJ对爬虫的检测非常严格,requests.get()默认的请求头太“素”了,很容易被识别成机器人。哪怕你有付费订阅,请求里没带登录后的Cookie和真实浏览器的User-Agent,服务器只会返回未登录的页面或者空内容,自然爬不到文章。
2. CSS选择器完全失效
你用的那些选择器(比如.items.hedSumm li > a)应该是很久以前的了,WSJ的页面结构早就更新了。现在搜索结果页的文章链接、文章页的日期/标签/标题选择器都变了,旧选择器找不到任何元素,循环根本没执行,所以啥输出都没有。
3. 异常处理太“沉默”
你的代码只捕获了请求异常,但没处理“选择器找不到元素”这类错误(比如date[0].text如果date是空列表,会直接抛出IndexError),不过你说没报错,大概率是搜索页的选择器没找到任何item,外层循环直接跳过了,连错误都没机会抛。
二、具体修正步骤
1. 必须添加自定义请求头
打开浏览器,登录WSJ后,按F12打开开发者工具,在「网络」标签里找任意一个WSJ的请求,复制它的User-Agent和Cookie,加到你的代码里:
headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36', 'Cookie': '这里粘贴你浏览器里的Cookie字符串' }
之后所有requests.get()都要带上这个headers参数,比如requests.get(url.format(page), headers=headers)。
2. 更新CSS选择器
打开WSJ的搜索页,用浏览器开发者工具(F12)查看当前元素结构,替换旧选择器:
- 搜索结果页的文章链接:现在可以用
div.search-results__item a[data-testid="search-result-item"](如果不对,自己在页面上右键检查元素确认) - 文章页日期:
time[data-testid="timestamp"] - 文章标签:
a.article-breadCrumb__link - 文章标题:
h1[data-testid="article-heading"] - 文章内容:
div.article-content p
3. 完善调试和异常处理
在代码里加一些打印信息,比如当前爬取的页码、找到的文章数量,方便你排查问题;同时捕获更多异常,避免程序悄悄崩溃:
print(f"正在爬取第 {page} 页...") items = soup.select("你的新选择器") print(f"找到 {len(items)} 篇文章") if not items: print("未找到文章,可能选择器失效或被反爬")
三、修正后的完整代码
import time import requests from bs4 import BeautifulSoup # 替换成你自己的Cookie和User-Agent headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36', 'Cookie': '这里粘贴浏览器中WSJ登录后的Cookie' } url = 'https://www.wsj.com/search/term.html?KEYWORDS=cybersecurity&min-date=2018/04/01&max-date=2019/03/31' \ '&isAdvanced=true&daysback=90d&andor=AND&sort=date-desc&source=wsjarticle,wsjpro&page={}' pages = 32 for page in range(1, pages+1): print(f"===== 开始爬取第 {page} 页 =====") try: res = requests.get(url.format(page), headers=headers) res.raise_for_status() # 检查请求是否成功(比如403、500错误) soup = BeautifulSoup(res.text, "lxml") # 更新为当前有效的文章链接选择器,需自行验证 items = soup.select('div.search-results__item a[data-testid="search-result-item"]') print(f"找到 {len(items)} 条文章链接") for idx, item in enumerate(items): _href = item.get("href") if not _href.startswith('http'): _href = "https://www.wsj.com" + _href print(f"正在爬取第 {page} 页第 {idx+1} 篇文章: {_href}") try: resp = requests.get(_href, headers=headers) resp.raise_for_status() sauce = BeautifulSoup(resp.text, "lxml") # 提取日期,处理找不到元素的情况 date_elem = sauce.select_one('time[data-testid="timestamp"]') date = date_elem.text.strip() if date_elem else "日期未知" # 提取标签 tag_elem = sauce.select_one('a.article-breadCrumb__link') tag = tag_elem.text.strip() if tag_elem else "标签未知" # 提取标题 title_elem = sauce.select_one('h1[data-testid="article-heading"]') title = title_elem.text.strip() if title_elem else "标题未知" # 提取内容 content_elems = sauce.select('div.article-content p') content = '\n'.join([elem.text.strip() for elem in content_elems]) if content_elems else "内容为空" print(f'{date}\n{tag}\n{title}\n{content}\n{"-"*50}') except Exception as e: print(f"爬取文章失败: {str(e)}") continue time.sleep(2) # 降低单页内的请求频率 except Exception as e: print(f"爬取第 {page} 页失败: {str(e)}") time.sleep(3) # 页面间的间隔时间
四、额外注意事项
- Cookie会过期:过一段时间后,Cookie失效了要重新从浏览器复制。
- 控制请求频率:不要把sleep时间设得太短,WSJ会封IP,建议每页间隔3-5秒,每篇文章间隔2-3秒。
- 定期检查选择器:WSJ经常更新页面结构,爬取前一定要用开发者工具确认选择器是否有效。
- 合规性:爬取付费内容要遵守WSJ的用户协议,不要商用或过度爬取。
内容的提问来源于stack exchange,提问作者Piyush Ghasiya

