Python 是迄今为止用于网络抓取的最流行的语言。它易于学习,拥有庞大的社区和庞大的库生态系统。
在这篇快速概述文章中,我们将看看每个网络抓取工具都应该知道的前 10 个网络抓取包。涵盖各种利基市场,如:
- HTTP 连接
- 浏览器自动化
- HTML 和 JSON 解析
- 数据质量和验证
Parsel 和 LXML
LXML是一种用于 Python 的快速且功能丰富的 HTML/XML 解析器。它是 C 库的包装器libxml2
,是在 Python 中解析 HTML 的最快、最可靠的方法。
LXML 使用强大而灵活的XPath 选择器来查找 HTML 中的元素:
from lxml import html html = """ <html> <title>My Title</title> <body> <div class="title">Product Title</div> </body> </html> """ tree = html.fromstring(html) print(tree.xpath("//title/text()")) ['My Title'] print(tree.xpath("//div[@class='title']/text()")) ['Product Title']
更进一步,lxml
由Parsel扩展,它简化了 API 的使用并添加了CSS 选择器支持:
from parsel import Selector html = """ <html> <title>My Title</title> <body> <div class="title">Product Title</div> </body> </html> """ selector = Selector(html) # use XPath print(selector.xpath("//title/text()").get()) "My Title" # or CSS selectors print(selector.css(".title::text").get()) "Product Title"
Parsel 正在成为解析大量 HTML 文档的实际方式,因为它将速度与lxml
CSS 和 XPath 选择器的易用性结合在一起。
HTTPX
HTTPX是迄今为止最完整、最现代的 Python HTTP 客户端包。它受到流行的请求库的启发,但通过支持现代 Python 和 HTTP 功能,它更进一步。
首先,它支持异步和同步 Python API。这使得扩展基于 httpx 的请求变得容易,同时还可以在简单的脚本中保持可访问性:
import httpx # use Sync API with httpx.Client() as client: response = client.get("https://web-scraping.dev/product/1") print(response.text) # use Async API: import asyncio async def run(): async with httpx.AsyncClient() as client: response = await client.get("https://web-scraping.dev/product/1") print(response.text) asyncio.run(run())
HTTPX 还支持 HTTP/2,它不太可能被阻止,因为大多数真实的人类流量都使用 http2/3:
with httpx.Client(http2=True) as client: response = client.get("https://web-scraping.dev/product/1") print(response.text)
最后,httpx
尊重 HTTP 标准和表单请求,更类似于真正的 Web 浏览器,这可以大大降低被阻止的可能性。就像尊重标题顺序一样。
BeautifulSoup
Beautifulsoup(又名 bs4)是 Python 中的另一个 HTML 解析器库。尽管远不止于此。
与 LXML 和 Parsel 不同,bs4 支持使用可访问的 Pythonic 方法进行解析,例如find
and find_all
:
from bs4 import BeautifulSoup html = """ <html> <title>My Title</title> <body> <div class="title">Product Title</div> </body> </html> """ soup = BeautifulSoup(html, 'lxml') # note: bs4 can use lxml under the hood which makes it super fast! # find single element by node name print(soup.find("title").text) "My Title" # find multiple using find_all and attribute matching for element in soup.find_all("div", class_="title"): print(element.text)
对于初学者来说,这种方法比 CSS 或 XPath 选择器更易于访问和可读,并且在处理高度复杂的 HTML 文档时通常更容易维护和开发。
BeautilfulSoup4 还带有许多实用功能,如 HTML 格式化和编辑。例如:
from bs4 import BeautifulSoup html = """ <div><h1>The Avangers: </h1><a>End Game</a><p>is one of the most popular Marvel movies</p></div> """ soup = BeautifulSoup(html) soup.prettify() """ <html> <body> <div> <h1> The Avangers: </h1> <a> End Game </a> <p> is one of the most popular Marvel movies </p> </div> </body> </html> """
以及许多其他实用功能,如 HTML 修改、选择性解析和清理。
JMESPath 和 JSONPath
JMESPath和JSONPath是两个允许您使用类似于 XPath 的查询语言查询 JSON 数据的库。
随着 JSON 在网络抓取空间中变得越来越大,这些库变得必不可少。
例如,使用 JMESPath,我们可以轻松查询、重塑和展平 JSON 数据集:
data = { "people": [ { "name": "Foo", "age": 33, "addresses": [ "123 Main St", "California", "US" ], "primary_email": "[email protected]", "secondary_email": "[email protected]", }, ... ] } jmespath.search(""" people[].{ first_name: name, age_in_years: age, address: addresses[0], state: addresses[1], country: addresses[2], emails: [primary_email, secondary_email] } """, data) [ { 'address': '123 Main St', 'state': 'California' 'country': 'US', 'age_in_years': 33, 'emails': ['[email protected]', '[email protected]'], 'first_name': 'Foo', }, ... ]
此功能在抓取隐藏的 Web 数据时特别有用,这可能会导致难以导航和消化的大量JSON 数据集。
或者,JSONPath 不太关注重塑数据集,而更多地关注从复杂的、高度嵌套的 JSON 数据集中选择值,并支持高级匹配函数:
import jsonpath_ng.ext as jp data = { "products": [ {"name": "Apple", "price": 12.88, "tags": ["fruit", "red"]}, {"name": "Peach", "price": 27.25, "tags": ["fruit", "yellow"]}, {"name": "Cake", "tags": ["pastry", "sweet"]}, ] } # find all product names: query = jp.parse("products[*].name") for match in query.find(data): print(match.value) # find all products with price > 20 query = jp.parse("products[?price>20].name") for match in query.find(data): print(match.value)
JSONPath 的杀手级功能是递归$..
选择器,它允许通过键快速选择数据集中任何位置的值(类似于心爱的 XPath//
选择器):
import jsonpath_ng.ext as jp data = { "products": [ {"name": "Apple", "price": 12.88}, {"name": "Peach", "price": 27.25}, {"name": "Cake"}, {"multiproduct": [{"name": "Carrot"}, {"name": "Pineapple"}]} ] } # find all "name" fields no matter where they are: query = jp.parse("$..name") for match in query.find(data): print(match.value)
Playwright 和 Selenium
无头浏览器在网络抓取中变得非常流行,作为处理动态 javascript 和抓取器阻塞的一种方式。
许多网站使用复杂的前端,通过后台请求或 javascript 函数按需生成数据。要访问这些数据,我们需要一个 javascript 执行环境,没有比使用真正的无头 Web 浏览器更好的方法了。
Javascript 指纹识别也正在成为抓取许多现代网站的必要步骤。
因此,为了在 Python 中控制用于网络抓取的无头浏览器,我们有两个流行的库:Playwright和Selenium。
Selenium是最早的主要浏览器自动化库之一,它几乎可以做浏览器可以做的任何事情:
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.chrome.options import Options from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import time # configure browser options = Options() options.headless = True options.add_argument("--window-size=1920,1080") options.add_argument("start-maximized") driver = webdriver.Chrome(options=options) driver.get("https://web-scraping.dev/product/1") # wait for page to load by waiting for reviews to appear on the page element = WebDriverWait(driver=driver, timeout=5).until( EC.presence_of_element_located((By.CSS_SELECTOR, '#reviews')) ) # click buttons: button = wait.until(EC.element_to_be_clickable((By.ID, "load-more-reviews"))) button.click() # return rendered HTML: print(driver.page_source)
或者,Playwright是浏览器自动化的现代版本,在现代异步和同步 API 中提供所有这些功能:
from playwright.sync_api import sync_playwright # Using synchronous Python: with sync_playwright() as p: browser = p.chromium.launch(headless=True) context = browser.new_context(viewport={'width': 1920, 'height': 1080}) page = context.new_page() page.goto("https://web-scraping.dev/product/1") # wait for the element to be present in the DOM page.wait_for_selector('#reviews') # click the button page.wait_for_selector("#load-more-reviews", state="attached") page.click("#load-more-reviews") # print the HTML print(page.content()) browser.close() # or asynchronous import asyncio from playwright.async_api import async_playwright async def run(): async with async_playwright() as p: browser = await p.chromium.launch(headless=True) context = await browser.new_context(viewport={'width': 1920, 'height': 1080}) page = await context.new_page() await page.goto("https://web-scraping.dev/product/1") # wait for the element to be present in the DOM await page.wait_for_selector('#reviews') # click the button await page.wait_for_selector("#load-more-reviews", state="attached") await page.click("#load-more-reviews") # print the HTML print(await page.content()) await browser.close() asyncio.run(run())
Cerberus 和 Pydantic
一个经常被忽视的网络抓取过程是数据质量保证步骤。Web 抓取是一个独特的利基市场,其中结果数据集高度动态,使得质量测试非常困难。
幸运的是,有几种工具可以帮助确保网络抓取数据的质量。
对于需要实时数据验证的网络抓取应用程序,Pydantic是一个不错的选择。Pydantic 允许指定可用于验证和变形抓取数据的严格数据模型:
from typing import Optional from pydantic import BaseModel, validator # example for scraped company data class Company(BaseModel): # define allowed field names and types: size: int founded: int revenue_currency: str hq_city: str hq_state: Optional[str] # some fields can be optional (i.e. have value of None) # then we can define any custom validation functions: @validator("size") def must_be_reasonable_size(cls, v): if not (0 < v < 20_000): raise ValueError(f"unreasonable company size: {v}") return v @validator("founded") def must_be_reasonable_year(cls, v): if not (1900 < v < 2022): raise ValueError(f"unreasonable found date: {v}") return v @validator("hq_state") def looks_like_state(cls, v): if len(v) != 2: raise ValueError(f'state should be 2 character long, got "{v}"') return v
对于需要更大灵活性和不太严格的数据验证的数据网络抓取工具,Cerberus是一个不错的选择。Cerberus 是一个模式验证库,允许使用简单的字典语法指定数据模型:
from cerberus import Validator def validate_name(field, value, error): """function for validating""" if "." in value: error(field, f"contains a dot character: {value}") if value.lower() in ["classified", "redacted", "missing"]: error(field, f"redacted value: {value}") if "<" in value.lower() and ">" in value.lower(): error(field, f"contains html nodes: {value}") schema = { "name": { # name should be a string "type": "string", # between 2 and 20 characters "minlength": 2, "maxlength": 20, # extra validation "check_with": validate_name, }, } v = Validator(schema) v.validate({"name": "H."}) print(v.errors) # {'name': ['contains a dot character: H.']}
Cerberus 的杀手级功能是易用性和定义灵活模式的能力,这些模式可以处理高度动态的网络抓取数据。
Scrapfly Python SDK
Scrapfly 是一个网络抓取 API,它通过各种功能增强网络抓取,例如:
- 来自50 多个代理国家/地区的住宅代理
- 防刮保护旁路
- Javascript 渲染
所有这些都可以通过Python SDK轻松访问,它利用了本文中介绍的工具。
例如,可以使用.selector
Scrapfly 结果的属性直接解析 HTML:
from scrapfly import ScrapeConfig, ScrapflyClient scrapfly = ScrapflyClient(key="YOUR KEY") result = scrapfly.scrape(ScrapeConfig("https://httpbin.dev/html")) # scrapfly result build parsel.Selector automatically: page_title = result.selector.xpath("//h1/text()").get() "Herman Melville - Moby-Dick"
结 论
在本文中,我们介绍了用于网络抓取的前 10 个 Python 包,它们涵盖了网络抓取过程的几个步骤。
对于网络抓取连接,我们已经介绍了httpx
它是一个出色的、功能丰富的 HTTP 客户端。另一方面,有selenium
并且playwright
使用了一个完整的无头网络浏览器。
对于数据解析,我们了解了三种最流行的 HTML 解析工具:lxml
,parsel
和beautifulsoup
. 用于 JSON 解析jmespath
,jsonpath
在网络抓取环境中工作得很好。
最后,对于网络抓取过程的最后一步,我们研究了两种不同的数据验证工具:严格的pydantic
和灵活的cerberus
。