为什么 urllib2.urlopen 读取 IMDb 页面时返回空内容?
结论
现有信息还不足以确定 IMDb 为什么返回空内容。先记录实际的 HTTP 状态码、最终 URL、响应头和响应体长度,再根据结果排查。浏览器能显示页面,不代表 urllib2 发出的请求会得到相同响应;开发者工具稍后才显示 HTML,也不能单凭这一点认定页面依赖 JavaScript。
代码里还有一个独立问题:for line in con 会消耗响应流,返回给调用方时,con 通常已经到达末尾,再读取就会得到空内容。这不能解释“第一次循环就执行零次”的情况,两者需要分别排查。
先记录服务器实际返回了什么
下面沿用原问题中的 Python 2 和 urllib2 写法。代码将响应体一次性读入变量,返回字节内容,避免把已经读完的响应对象交给调用方。
from __future__ import print_function
import urllib2
USER_AGENT = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/96.0.4664.45 Safari/537.36"
)
def openConnection(url):
req = urllib2.Request(url, headers={
'User-Agent': USER_AGENT
})
try:
con = urllib2.urlopen(req, timeout=30)
except urllib2.HTTPError as exc:
print('HTTP status:', exc.code)
print('Headers:', exc.info())
try:
print('Error body:', repr(exc.read(500)))
finally:
exc.close()
raise
except urllib2.URLError as exc:
print('Request failed:', exc.reason)
raise
try:
body = con.read()
print('Status:', con.getcode())
print('Final URL:', con.geturl())
print('Headers:', con.info())
print('Body length:', len(body))
print('Body preview:', repr(body[:500]))
return body
finally:
con.close()
def readIMDB(movie):
url = 'https://www.imdb.com/title/' + movie + '/fullcredits'
return openConnection(url)
data = readIMDB('tt0084726')
repr() 可以显示换行、空格和部分不可见字节,帮助区分是确实没有响应体,还是打印后看不出内容。调试输出中的 Body length 是实际读取到的字节数。
根据输出定位问题
- 如果响应体长度为 0,检查状态码和响应头。
204表示没有响应内容;即使状态码是200,也不能据此断言服务器返回了目标页面。还需要比较浏览器请求与脚本请求。 - 如果最终 URL 与请求地址不同,说明请求发生了重定向。确认最终落到的是目标页面、登录页面,还是其他页面。
- 如果出现 HTTP 错误或验证页面,根据实际状态码和响应内容处理。现有材料无法确认 IMDb 是否对这次请求实施了访问限制。
- 如果响应体有内容,但找不到演职员数据,检查拿到的是完整页面、页面框架,还是其他响应,再判断是否需要处理动态加载。
- 如果函数内部能读到内容,调用方却读不到,检查是否重复读取了同一个响应流。上面的代码通过返回
body避免了这一问题。
对照浏览器的原始响应
等待请求完成后,在浏览器开发者工具的 Network 面板中检查目标文档请求的状态码、重定向、请求头和 Response 内容,再与脚本输出比较。
注意区分 Network 中的原始响应和 Elements 中经过脚本修改后的 DOM。如果数据只在后续的 Fetch/XHR 请求中出现,urllib2 读取初始页面时不会自动取得这些数据,因为它不会执行 JavaScript。确认存在这种情况后,再考虑使用允许访问的数据接口或浏览器自动化。
浏览器还可能携带 Cookie 或其他会话信息,只复制 User-Agent 不能保证两边请求一致。对比时不要公开 Cookie、令牌等敏感值。
原代码中还需要修正的地方
原样粘贴的代码缺少有效缩进,还使用了 ssl 却没有展示对应的导入。如果实际文件也是这样,应先修正;如果只是粘贴时丢失,可以忽略这个格式问题。
裸 except: 会隐藏具体异常,打印 Connection failed. Retrying. 也不会真正触发重试。调试时应保留异常类型和错误信息。
ssl._create_unverified_context() 会关闭证书校验,不能用来解决空响应问题。上面的示例使用默认 HTTPS 证书校验;如果实际出现证书错误,应另行排查证书信任配置。
对于已经完成并返回空响应体的请求,在 urlopen() 后增加等待不会让响应流自动补出页面内容。先查清收到的响应,再决定是否需要重新请求或采用其他获取方式。
备注:内容仅供参考。
