3.2 动态内容抓取


文档摘要

3.2 动态内容抓取 3.2 Scrapy 动态内容抓取 3.2.1 动态内容抓取的挑战 传统的 Scrapy 爬虫主要依赖于解析 HTML 源码来提取数据。然而,现代网站越来越多地使用 JavaScript 来动态生成内容,这意味着页面的初始 HTML 源码可能不包含所有需要的数据。这些数据通常在页面加载后,通过 JavaScript 从服务器获取并动态添加到页面中。 例如,考虑一个在线购物网站,商品价格和库存信息可能不是直接嵌入在 HTML 中,而是通过 AJAX 请求从服务器加载并动态显示。如果只抓取初始 HTML 源码,就无法获取这些动态生成的数据。 解决动态内容抓取的关键在于模拟浏览器行为,执行 JavaScript 代码,并获取动态渲染后的页面内容。 3.2.

3.2 动态内容抓取

3.2 Scrapy 动态内容抓取

3.2.1 动态内容抓取的挑战

传统的 Scrapy 爬虫主要依赖于解析 HTML 源码来提取数据。然而,现代网站越来越多地使用 JavaScript 来动态生成内容,这意味着页面的初始 HTML 源码可能不包含所有需要的数据。这些数据通常在页面加载后,通过 JavaScript 从服务器获取并动态添加到页面中。

例如,考虑一个在线购物网站,商品价格和库存信息可能不是直接嵌入在 HTML 中,而是通过 AJAX 请求从服务器加载并动态显示。如果只抓取初始 HTML 源码,就无法获取这些动态生成的数据。

解决动态内容抓取的关键在于模拟浏览器行为,执行 JavaScript 代码,并获取动态渲染后的页面内容。

3.2.2 解决方案:Scrapy + Selenium

Selenium 是一个自动化测试工具,可以模拟用户在浏览器中的操作,例如点击按钮、填写表单等。通过结合 Scrapy 和 Selenium,可以实现动态内容抓取。

工作原理:

  1. Scrapy Spider 发起请求 (Request)。

  2. 请求经过 Middleware (例如 Selenium Middleware)。

  3. Middleware 使用 Selenium Driver 控制浏览器。

  4. Selenium Driver 加载页面,执行 JavaScript 代码。

  5. 浏览器动态渲染页面,生成包含动态内容的 HTML。

  6. Middleware 获取动态渲染后的 HTML 源码。

  7. Middleware 创建包含动态内容的 Response 对象。

  8. Response 对象返回给 Scrapy Spider 进行解析。

3.2.3 代码实践

1. 安装依赖:

pip install scrapy selenium webdriver-manager

2. 创建 Scrapy 项目:

scrapy startproject dynamic_scraping cd dynamic_scraping scrapy genspider dynamic_spider example.com

3. 定义 Item (items.py):

import scrapy class DynamicScrapingItem(scrapy.Item): title = scrapy.Field() price = scrapy.Field() # 其他需要抓取的字段 pass

4. 创建 Selenium Middleware (middlewares.py):

from scrapy import signals from scrapy.http import HtmlResponse from selenium import webdriver from selenium.webdriver.chrome.options import Options from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class SeleniumMiddleware: def __init__(self): chrome_options = Options() chrome_options.add_argument("--headless") # 无头模式,不显示浏览器界面 chrome_options.add_argument("--disable-gpu") chrome_options.add_argument("--no-sandbox") # 解决某些环境下的权限问题 self.driver = webdriver.Chrome(ChromeDriverManager().install(), options=chrome_options) def __del__(self): self.driver.quit() def process_request(self, request, spider): if spider.name == 'dynamic_spider' and 'use_selenium' in request.meta and request.meta['use_selenium']: self.driver.get(request.url) # 等待动态内容加载 (例如,等待某个元素出现) try: WebDriverWait(self.driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, ".product-price")) ) except: print("Timeout waiting for element to load.") body = self.driver.page_source return HtmlResponse(self.driver.current_url, body=body, encoding='utf-8', request=request) @classmethod def from_crawler(cls, crawler): middleware = cls() crawler.signals.connect(middleware.spider_closed, signal=signals.spider_closed) return middleware def spider_closed(self, spider): self.driver.quit()

代码解释:

  • __init__:初始化 Selenium WebDriver,使用 Chrome 浏览器和 ChromeDriverManager 来自动管理 ChromeDriver。 --headless 参数使浏览器在后台运行,不显示界面。

  • process_request:处理请求的核心方法。

    • 检查 spider 的名称和 use_selenium meta 标志,以确定是否使用 Selenium 处理该请求。

    • 使用 self.driver.get(request.url) 加载页面。

    • 使用 WebDriverWait 等待动态内容加载。EC.presence_of_element_located 是一个期望条件,等待 CSS 选择器 .product-price 对应的元素出现在页面中。

    • 获取动态渲染后的 HTML 源码 self.driver.page_source

    • 创建 HtmlResponse 对象,包含动态内容,并返回给 Scrapy。

  • from_crawlerspider_closed:用于管理 Selenium WebDriver 的生命周期,在爬虫关闭时关闭浏览器。

5. 启用 Middleware (settings.py):

DOWNLOADER_MIDDLEWARES = { 'dynamic_scraping.middlewares.SeleniumMiddleware': 543, }

6. 修改 Spider (dynamic_spider.py):

import scrapy from dynamic_scraping.items import DynamicScrapingItem class DynamicSpider(scrapy.Spider): name = "dynamic_spider" allowed_domains = ["example.com"] start_urls = ["http://example.com/dynamic-page"] # 替换为包含动态内容的页面 URL def start_requests(self): for url in self.start_urls: yield scrapy.Request(url, callback=self.parse, meta={'use_selenium': True}) def parse(self, response): # 使用 CSS 选择器或 XPath 提取数据 title = response.css("h1::text").get() price = response.css(".product-price::text").get() item = DynamicScrapingItem() item['title'] = title item['price'] = price yield item

代码解释:

  • start_requests:覆盖默认的 start_requests 方法,为每个请求添加 meta={'use_selenium': True},告诉 Selenium Middleware 处理这些请求。

  • parse:使用 CSS 选择器或 XPath 从动态渲染后的 HTML 中提取数据。

7. 运行爬虫:

scrapy crawl dynamic_spider

3.2.4 处理复杂动态内容

1. 滚动加载:

有些网站通过滚动页面来加载更多内容。可以使用 Selenium 的 execute_script 方法来模拟滚动操作。

# 在 SeleniumMiddleware 的 process_request 方法中 self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") # 等待一段时间,让新内容加载 time.sleep(2)

2. 点击按钮:

有些网站需要点击按钮才能加载更多内容。可以使用 Selenium 的 find_elementclick 方法来模拟点击操作。

# 在 SeleniumMiddleware 的 process_request 方法中 button = self.driver.find_element(By.CSS_SELECTOR, ".load-more-button") button.click() # 等待一段时间,让新内容加载 time.sleep(2)

3. 处理 AJAX 请求:

有时需要直接捕获 AJAX 请求返回的数据。可以使用 Selenium 的 get_log 方法来获取网络请求日志。

# 在 SeleniumMiddleware 的 process_request 方法中 self.driver.get(request.url) # 启用性能日志 self.driver.execute_script("window.performance.clearResourceTimings();") # 执行一些操作,触发 AJAX 请求 # ... # 获取性能日志 logs = self.driver.get_log('performance') for log in logs: message = log['message'] try: message_json = json.loads(message) method = message_json['message']['method'] if method == 'Network.responseReceived': url = message_json['message']['params']['response']['url'] # 检查 URL 是否是目标 AJAX 请求 if 'api/products' in url: # 获取响应内容 response = requests.get(url).json() # 处理响应数据 # ... except json.JSONDecodeError: pass

4. 使用 JavaScript 注入:

可以直接使用 Selenium 的 execute_script 方法在页面中执行 JavaScript 代码,例如修改页面元素或提取数据。

# 在 SeleniumMiddleware 的 process_request 方法中 # 提取页面上的所有链接 links = self.driver.execute_script("return Array.from(document.querySelectorAll('a')).map(a => a.href);")

3.2.5 优化技巧

  • 使用无头模式 (headless mode):ChromeOptions 中添加 --headless 参数,使浏览器在后台运行,减少资源消耗。

  • 设置合理的等待时间: 使用 WebDriverWait 设置合理的等待时间,避免因为页面加载缓慢而导致爬虫失败。

  • 使用代理 IP: 使用代理 IP 可以避免 IP 被封禁。

  • 优化 CSS 选择器和 XPath: 使用高效的 CSS 选择器和 XPath 可以提高数据提取速度。

  • 避免过度抓取: 遵守网站的 Robots.txt 协议,避免过度抓取,以免给网站带来不必要的负担。

  • 缓存数据: 对已经抓取过的数据进行缓存,避免重复抓取。

  • 使用异步请求: 使用 asyncioaiohttp 可以实现异步请求,提高抓取效率。

3.2.6 总结

使用 Scrapy 结合 Selenium 可以有效地抓取动态内容。 通过 Selenium 模拟浏览器行为,执行 JavaScript 代码,可以获取动态渲染后的页面内容。 需要注意的是,动态内容抓取通常比静态内容抓取更复杂,需要更多的调试和优化。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U