杰瑞科技汇

Python爬虫如何解析JavaScript渲染内容?

对于初学者来说,一个常见的误解是:Python爬虫只能抓取静态网页(纯HTML),而遇到使用JavaScript动态加载内容的网站就束手无策了,这个观点既对也不对。

  • 对的部分:最简单的爬虫(如使用 requests + BeautifulSoup)确实无法执行JavaScript,所以无法获取到由JS动态生成的内容。
  • 不对的部分:Python爬虫社区已经发展出非常成熟的工具来处理JavaScript渲染问题,我们完全可以利用Python来“驱动”一个浏览器,让它像真人一样去执行JS,然后我们再获取最终渲染好的页面内容。

下面我将从几个层面详细解释这个问题,并提供代码示例。


为什么需要处理JavaScript?

现代网站为了提升用户体验,大量使用JavaScript(特别是AJAX/Fetch API)来动态加载数据,典型的场景包括:

  • 无限滚动:你向下滚动页面时,内容才从服务器加载并显示出来。
  • 异步加载数据:页面初始加载时只有框架,数据通过API请求异步获取并填充到页面上,电商的商品列表、社交媒体的动态信息流。
  • 单页应用:整个网站就是一个HTML文件,所有页面切换和数据展示都依赖于JavaScript在客户端完成,Gmail、网易云音乐、知乎等。

对于这类网站,直接用 requests 请求得到的HTML可能是空的,或者只包含一个加载中的骨架,而真正你需要的数据根本不在初始的HTML源码里。


Python爬虫如何与JavaScript交互?

主要有三种策略,从简单到复杂,适用于不同的场景。

直接分析网络请求(推荐首选)

这是最优雅、最高效的方法,也是专业爬虫工程师的首选。

核心思想:浏览器能看到的,爬虫也应该能看到,当页面上的JavaScript发起网络请求(如 fetch('https://api.example.com/data')$.ajax(...)) 时,你可以在浏览器的“开发者工具” -> “网络” 面板中看到这个请求,我们可以直接模拟这个请求,获取JSON数据,完全绕过HTML解析和JS渲染。

操作步骤

  1. 打开浏览器开发者工具:通常按 F12
  2. 切换到“网络”选项卡
  3. 勾选“禁用缓存”(可选,但推荐)。
  4. 刷新页面或执行触发JS加载的操作(如滚动、点击)。
  5. 在请求列表中寻找目标数据:重点关注 XHR (XMLHttpRequest) 和 Fetch 类型的请求,通常这些请求的响应就是你想爬取的JSON数据。
  6. 分析请求信息:找到目标请求后,复制其 请求方法URL请求头,特别是 Request Payload (POST请求) 或 Query String Parameters (GET请求)。
  7. 用Python的 requests 库模拟请求

代码示例

假设你在“网络”面板中发现了一个请求 https://example.com/api/get_items,它返回了JSON格式的商品列表。

import requests
import json
# 1. 从浏览器开发者工具中复制过来的请求头
# 这可以防止被服务器识别为爬虫,提高成功率
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
    'Accept': 'application/json, text/plain, */*',
    'Referer': 'https://example.com/items' # 有些请求需要 Referer
}
# 2. 如果是GET请求,参数可能放在URL里
# params = {'page': 1, 'category': 'electronics'}
# 3. 如果是POST请求,数据在 Request Payload 里
data = {
    'page': 1,
    'sort': 'sales'
}
# 4. 发送请求
try:
    # response = requests.get('https://example.com/api/get_items', headers=headers, params=params)
    response = requests.post('https://example.com/api/get_items', headers=headers, json=data)
    response.raise_for_status()  # 如果请求失败 (状态码不是 2xx), 则抛出异常
    # 5. 解析JSON数据
    items_data = response.json()
    print(json.dumps(items_data, indent=2, ensure_ascii=False))
    # 6. 接下来就可以遍历 items_data 提取你需要的信息了
    # for item in items_data['data']:
    #     print(item['name'], item['price'])
except requests.exceptions.RequestException as e:
    print(f"请求失败: {e}")

优点

  • 速度快:无需加载整个页面,直接获取数据。
  • 效率高:不依赖浏览器,资源消耗小。
  • 稳定:不依赖于页面的DOM结构,只要API接口不变,爬虫就不会失效。

缺点

  • 有时请求参数可能经过复杂加密,难以逆向分析。

使用无头浏览器(终极武器)

当数据无法通过直接分析网络请求获取时(数据经过JS加密、或者请求参数是动态生成的),我们就需要让Python来“操控”一个真实的浏览器。

核心思想:启动一个没有图形界面的“无头浏览器”,让Python代码向这个浏览器发送指令(如打开页面、点击按钮、输入文字),浏览器会像真人一样执行JavaScript并渲染页面,然后我们获取浏览器中最终的HTML源码,再用 BeautifulSouplxml 进行解析。

常用库

  • Selenium:老牌、稳定、跨语言(支持Java, C#等),有丰富的API,但性能稍差。
  • Playwright:微软出品,新一代王者,性能更好,API更现代化,支持自动等待,对Chromium, Firefox, WebKit的兼容性极佳。(强烈推荐新项目使用)

以 Playwright 为例的代码示例

首先安装:pip install playwright,然后运行 playwright install 下载浏览器驱动。

from playwright.sync_api import sync_playwright
from bs4 import BeautifulSoup
import time
def scrape_with_playwright(url):
    with sync_playwright() as p:
        # 启动一个无头浏览器
        # headless=False 可以让你看到浏览器在做什么,方便调试
        browser = p.chromium.launch(headless=False)
        # 打开一个新页面
        page = browser.new_page()
        # 设置一个更真实的 User-Agent
        page.set_extra_http_headers({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
        })
        print(f"正在访问: {url}")
        page.goto(url)
        # 等待特定元素出现,表示数据已经加载完成
        # 这比 time.sleep() 更可靠
        # 等待一个商品列表的容器加载出来
        page.wait_for_selector('.product-list-container') # 请替换为网站上的实际选择器
        # 获取页面内容(是JS执行渲染后的最终HTML)
        html_content = page.content()
        # 关闭浏览器
        browser.close()
        return html_content
# 使用 BeautifulSoup 解析获取到的HTML
url_to_scrape = "https://example.com/dynamic-items-page"
html = scrape_with_playwright(url_to_scrape)
soup = BeautifulSoup(html, 'html.parser')
# 假设商品都在 class="item" 的 div 标签里
items = soup.find_all('div', class_='item')
for item in items:
    name_tag = item.find('h2', class_='product-name')
    price_tag = item.find('span', class_='price')
    if name_tag and price_tag:
        name = name_tag.get_text(strip=True)
        price = price_tag.get_text(strip=True)
        print(f"商品: {name}, 价格: {price}")

优点

  • 万能:可以处理任何复杂的JS渲染和交互。
  • 逼真:行为与真实用户几乎无异,可以绕过很多基于JS的检测。

缺点

  • 速度慢:启动和操作浏览器非常耗时。
  • 资源消耗大:占用大量CPU和内存。
  • 不稳定:网站更新DOM结构或JS逻辑,爬虫可能失效。

使用能执行JS的HTTP库(轻量级方案)

这类库在 requests 的基础上,集成了一个轻量级的JS运行时(如PyExecJS),可以在不启动完整浏览器的情况下执行部分JS。

代表库

  • requests-html:基于 requestspyppeteer(无头Chrome的Python封装),支持JS渲染。

代码示例(requests-html

首先安装:pip install requests-html

from requests_html import HTMLSession
url = "https://example.com/dynamic-content"
# 创建一个会话
session = HTMLSession()
# 发起请求,HTMLSession会自动处理JS渲染
print(f"正在获取并渲染页面: {url}")
response = session.get(url)
# 默认会等待JS执行一段时间
# 你也可以手动等待特定元素
# response.html.render(timeout=20) # 如果默认时间不够,可以手动渲染
# 获取渲染后的HTML
html_content = response.html.html
# 也可以直接使用CSS选择器提取元素,功能类似BeautifulSoup
# 提取所有标题s = response.html.find('h2')
print(f"找到 {len(titles)} 个标题:")in titles:
    print(title.text)

优点

  • 比无头浏览器更轻量,使用起来比Selenium/Playwright更简单。
  • API友好,对 requests 用户很友好。

缺点

  • 功能和性能介于 requestsPlaywright 之间。
  • 仍然比纯 requests 慢,且可能无法处理极其复杂的JS交互。

总结与选择策略

方法 优点 缺点 适用场景
分析网络请求 速度快、效率高、稳定 需要手动分析,请求可能加密 首选方案,适用于绝大多数动态网站(电商、社交媒体、新闻资讯等)
无头浏览器 功能最强大、最逼真 速度慢、资源消耗大、不稳定 无法通过API获取数据时(如JS加密、复杂交互、登录后动态内容)
JS-HTTP库 轻量、易用 性能和功能有限 简单的JS渲染场景,不想用Selenium/Playwright时的折中选择

给你的建议

  1. 永远从“分析网络请求”开始,打开浏览器的开发者工具,花10分钟时间看看有没有直接的API调用,这会为你节省大量的时间和计算资源。
  2. 如果找不到API,或者分析过程过于复杂,再考虑使用无头浏览器Playwright 是这个领域的最佳选择。
  3. requests-html 可以作为一个不错的备选,当你觉得Selenium/Playwright有点“重”的时候可以试试。

掌握了这三种策略,你就可以应对市面上绝大多数网站的爬取挑战了。

分享:
扫描分享到社交APP
上一篇
下一篇