requests请求失败但页面为空常因反爬响应或缺失请求头;lxml解析空内容不报错却返回空列表;需检查status_code、添加User-Agent、用urljoin转绝对URL、过滤伪链接、处理JS渲染及命名空间,并用Session和随机延时防封。

requests请求失败但没报错,页面内容却是空的
常见于目标网站返回了反爬响应(比如 200 状态码但 HTML 是验证码页、跳转 JS 或空白模板),或者 requests.get() 没带必要请求头被静默拦截。lxml 解析空字符串会不报错但 tree.xpath() 返回空列表,容易误判为“没链接”。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 始终检查
r.status_code和r.text[:200](前 200 字符)确认是否真拿到目标 HTML - 至少添加
headers={'User-Agent': 'Mozilla/5.0...'},用浏览器真实 UA(可从 Chrome 开发者工具 Network 里复制) - 对含 JavaScript 渲染的页面,
requests无法执行 JS,此时lxml提取的永远是初始 HTML 中的静态链接——不是所有<a href></a>都会在首屏加载完成
用lxml提取标签时漏掉相对路径或JS跳转链接
lxml.etree.HTML 只解析 HTML 结构,不会自动补全 URL。它原样返回 href 属性值,不做任何归一化处理。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 用
urllib.parse.urljoin(base_url, href)统一转为绝对 URL,base_url应传入原始请求的 URL(不是首页) - 注意过滤
href值为#、javascript:void(0)、mailto:、tel:的伪链接,它们不是可抓取的目标 - 某些站点用
onclick="location.href='xxx'"或data-url属性存链接,lxml 不会自动识别——需额外写 XPath 匹配这些属性,例如//*[contains(@onclick,'href')]/@onclick,再正则提取
批量抓取时被封IP或触发频率限制
requests 默认无延迟、无 session 复用、无 cookies 管理,高频请求极易被识别为爬虫。很多网站对单 IP 每分钟请求数(QPS)做硬限制,超限后返回 403 或空内容。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 用
requests.Session()复用连接和 cookies,比反复新建requests.get()更隐蔽 - 在请求间加随机延时:
time.sleep(random.uniform(1, 3)),避免固定间隔暴露模式 - 若需长期运行,考虑轮换 User-Agent(从列表随机选)、搭配代理池(如
proxies={'https': 'http://user:pass@host:port'}),但代理质量差反而增加失败率
XPath表达式写对了却提不到链接?检查命名空间和HTML结构变异
部分网页使用 XHTML 或嵌入 SVG、MathML 等带命名空间的元素,lxml 默认会保留 namespace,导致 //a[@href] 匹配不到带前缀的 <a></a>;更常见的是网站前端改版后 DOM 结构变化,XPath 立即失效。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 解析前打印
tree.getroot().tag和tree.getroot().nsmap,确认是否存在命名空间;如有,用namespaces={'h': 'http://www.w3.org/1999/xhtml'}并改写 XPath 为//h:a[@href] - 不要依赖深层嵌套路径(如
//div[3]/section[1]/ul/li/a),优先用语义化属性定位://a[contains(@class, 'post-link') or @rel='bookmark'] - 每次更新规则前,先用
tree.xpath('//*')看顶层节点,再逐步缩小范围,比盲目猜 XPath 高效得多
真正难的不是写出第一版能跑的脚本,而是让同一段代码在目标网站改版三次后还能稳定提取出有效链接——这意味着你要把 XPath 写得足够健壮,把请求行为模拟得足够像人,还要随时准备应对 status_code 突然变成 429 或返回一段加密的 HTML 注释。


















