引言
在过去,收集数据是一项繁琐的工作,有时非常昂贵。机器学习项目不能没有数据。幸运的是,我们现在在网络上有很多数据可供我们使用。我们可以从 Web 复制数据来创建数据集。我们可以手动下载文件并将其保存到磁盘。但是,我们可以通过自动化数据收集来更有效地做到这一点。Python中有几种工具可以帮助自动化。
完成本教程后,您将学习:
- 如何使用
requests
库通过 HTTP 读取网络数据 - 如何使用
pandas
读取网页上的表格 - 如何使用
Selenium
模拟浏览器操作
概述
本教程分为三个部分;它们是:
- 使用
requests
库 - 使用
panda
在网络上阅读表格 - 用
selenium
阅读动态内容
使用requests库
当我们谈论编写Python程序从Web上读取时,不可避免的是我们无法避免requests
库。您需要安装它(以及我们稍后将介绍的BeautifulSoup和lxml):
pip install requests beautifulsoup4 lxml
它为您提供了一个界面,可让您轻松与网络进行交互。
非常简单的用例是从URL读取网页:
import requests
# Lat-Lon of New York
URL = "https://weather.com/weather/today/l/40.75,-73.98"
resp = requests.get(URL)
print(resp.status_code)
print(resp.text)
运行结果:
如果您熟悉 HTTP,您可能还记得状态代码 200 表示请求已成功完成。然后我们可以阅读响应。在上面,我们阅读文本响应并获取网页的HTML。如果它是CSV或其他一些文本数据,我们可以在响应对象的属性中获取它们。例如text
,这就是我们从美联储经济学数据中读取CSV的方法:
以下例子会用到pandas 模块 :
安装: pip install pandas -i https://pypi.tuna.tsinghua.edu.cn/simple
import io
import pandas as pd
import requests
URL = "https://fred.stlouisfed.org/graph/fredgraph.csv?id=T10YIE&cosd=2017-04-14&coed=2022-04-14"
resp = requests.get(URL)
if resp.status_code == 200:
csvtext = resp.text
csvbuffer = io.StringIO(csvtext)
df = pd.read_csv(csvbuffer)
print(df)
运行结果:
如果数据是JSON的形式,我们可以将其读取为文本,甚至可以为您解码。例如,以下是以JSON格式从GitHub中提取一些数据并将其转换为Python字典:
import requests
URL = "https://api.github.com/users/jbrownlee"
resp = requests.get(URL)
if resp.status_code == 200:
data = resp.json()
print(data)
运行结果:
但是,如果 URL 为您提供了一些二进制数据,例如 ZIP 文件或 JPEG 图像,则需要在属性中获取它们,因为这将是二进制数据。例如,这就是我们下载图像(维基百科的标志)的方式:
import requests
URL = "https://en.wikipedia.org/static/images/project-logos/enwiki.png"
wikilogo = requests.get(URL)
if wikilogo.status_code == 200:
with open("enwiki.png", "wb") as fp:
fp.write(wikilogo.content)
运行结果:
鉴于我们已经获得了网页,我们应该如何提取数据?这超出了requests
所能提供给我们的范围,但我们可以使用不同的library 来提供帮助。有两种方法可以做到这一点,这取决于我们想要如何指定数据。
第一种方法是将 HTML 视为一种 XML 文档,并使用 XPath 语言提取元素。在这种情况下,我们可以利用该库首先创建一个文档对象模型(DOM),然后通过XPath进行搜索:
import requests
from lxml import etree
URL = "https://weather.com/weather/today/l/40.75,-73.98"
resp = requests.get(URL)
# 从HTML文本创建DOM
dom = etree.HTML(resp.text)
# 搜索温度元素并获得内容
elements = dom.xpath("//span[@data-testid='TemperatureValue' and contains(@class,'CurrentConditions')]")
print(elements[0].text)
XPath 是一个字符串,它指定如何查找元素。lxml 对象提供了一个函数,用于在 DOM 中搜索与 XPath 字符串匹配的元素,这些元素可以是多个匹配项。上面的 XPath 意味着在具有标记和属性与 “” 匹配且以 “” 开头的任何位置查找 HTML 元素。我们可以通过检查HTML源代码从浏览器的开发人员工具(例如,下面的Chrome屏幕截图)中学习这一点。xpath()``<span>``data-testid``TemperatureValue``class``CurrentConditions
这个例子是找到纽约市的温度,由我们从这个网页获得的这个特定元素提供。我们知道与 XPath 匹配的第一个元素是我们需要的,我们可以读取标记内的文本。<span>
另一种方法是在HTML文档上使用CSS选择器,我们可以利用BeautifulSoup库:
import requests
from bs4 import BeautifulSoup
URL = "https://weather.com/weather/today/l/40.75,-73.98"
resp = requests.get(URL)
soup = BeautifulSoup(resp.text, "lxml")
elements = soup.select('span[data-testid="TemperatureValue"][class^="CurrentConditions"]')
print(elements[0].text)
运行结果:
在上面,我们首先将HTML文本传递给BeautifulSoup。BeautifulSoup支持各种HTML解析器,每个解析器具有不同的功能。在上面,我们使用库作为BeautifulSoup推荐的解析器(它通常也是最快的)。CSS选择器是一种不同的迷你语言,与XPath相比有优点和缺点。上面的选择器与我们在上一个示例中使用的 XPath 相同。因此,我们可以从第一个匹配的元素中获得相同的温度。
以下是根据网络上的实时信息打印纽约当前温度的完整代码:
import requests
from bs4 import BeautifulSoup
from lxml import etree
URL = "https://weather.com/weather/today/l/40.75,-73.98"
resp = requests.get(URL)
if resp.status_code == 200:
# Using lxml
dom = etree.HTML(resp.text)
elements = dom.xpath("//span[@data-testid='TemperatureValue' and contains(@class,'CurrentConditions')]")
print(elements[0].text)
# Using BeautifulSoup
soup = BeautifulSoup(resp.text, "lxml")
elements = soup.select('span[data-testid="TemperatureValue"][class^="CurrentConditions"]')
print(elements[0].text)
可以想象,您可以通过定期运行此脚本来收集温度的时间序列。同样,我们可以从各种网站自动收集数据。这就是我们为机器学习项目获取数据的方法。
使用 panda 在网络上阅读表格
通常,网页将使用表格来承载数据。如果页面足够简单,我们甚至可以跳过检查它以找出XPath或CSS选择器,并使用pandas一次性获取页面上的所有表格。它很简单,可以在一行中完成:
import pandas as pd
tables = pd.read_html("https://www.federalreserve.gov/releases/h10/current/")
print(tables)
[ Instruments 2022Apr7 2022Apr8 2022Apr11 2022Apr12 2022Apr13
0 Federal funds (effective) 1 2 3 0.33 0.33 0.33 0.33 0.33
1 Commercial Paper 3 4 5 6 NaN NaN NaN NaN NaN
2 Nonfinancial NaN NaN NaN NaN NaN
3 1-month 0.30 0.34 0.36 0.39 0.39
4 2-month n.a. 0.48 n.a. n.a. n.a.
5 3-month n.a. n.a. n.a. 0.78 0.78
6 Financial NaN NaN NaN NaN NaN
7 1-month 0.49 0.45 0.46 0.39 0.46
8 2-month n.a. n.a. 0.60 0.71 n.a.
9 3-month 0.85 0.81 0.75 n.a. 0.86
10 Bank prime loan 2 3 7 3.50 3.50 3.50 3.50 3.50
11 Discount window primary credit 2 8 0.50 0.50 0.50 0.50 0.50
12 U.S. government securities NaN NaN NaN NaN NaN
13 Treasury bills (secondary market) 3 4 NaN NaN NaN NaN NaN
14 4-week 0.21 0.20 0.21 0.19 0.23
15 3-month 0.68 0.69 0.78 0.74 0.75
16 6-month 1.12 1.16 1.22 1.18 1.17
17 1-year 1.69 1.72 1.75 1.67 1.67
18 Treasury constant maturities NaN NaN NaN NaN NaN
19 Nominal 9 NaN NaN NaN NaN NaN
20 1-month 0.21 0.20 0.22 0.21 0.26
21 3-month 0.68 0.70 0.77 0.74 0.75
22 6-month 1.15 1.19 1.23 1.20 1.20
23 1-year 1.78 1.81 1.85 1.77 1.78
24 2-year 2.47 2.53 2.50 2.39 2.37
25 3-year 2.66 2.73 2.73 2.58 2.57
26 5-year 2.70 2.76 2.79 2.66 2.66
27 7-year 2.73 2.79 2.84 2.73 2.71
28 10-year 2.66 2.72 2.79 2.72 2.70
29 20-year 2.87 2.94 3.02 2.99 2.97
30 30-year 2.69 2.76 2.84 2.82 2.81
31 Inflation indexed 10 NaN NaN NaN NaN NaN
32 5-year -0.56 -0.57 -0.58 -0.65 -0.59
33 7-year -0.34 -0.33 -0.32 -0.36 -0.31
34 10-year -0.16 -0.15 -0.12 -0.14 -0.10
35 20-year 0.09 0.11 0.15 0.15 0.18
36 30-year 0.21 0.23 0.27 0.28 0.30
37 Inflation-indexed long-term average 11 0.23 0.26 0.30 0.30 0.33,
pandas 中的函数read_html()
读取 URL 并查找页面上的所有表。每个表都转换为 pandas DataFrame,然后在列表中返回所有表。在这个例子中,我们正在阅读美联储的各种利率,而美联储恰好在这个页面上只有一个表格。表列由 pandas 自动标识。
很可能并非所有表格都是我们感兴趣的。有时,网页会使用表格作为格式化页面的一种方式,但熊猫可能不够聪明, 无法分辨。因此,我们需要测试并挑选函数返回的结果。
用硒阅读动态内容
现代网页的很大一部分充满了JavaScript。这给了我们一个更奇特的体验,但成为一个障碍,用作提取数据的程序。一个例子是雅虎的主页,如果我们只加载页面并找到所有新闻标题,那么在浏览器上看到的要少得多:
import requests
# Read Yahoo home page
from lxml import etree
URL = "https://www.yahoo.com/"
resp = requests.get(URL)
dom = etree.HTML(resp.text)
# Print news headlines
elements = dom.xpath("//h3/a[u[@class='StretchedBox']]")
for elem in elements:
print(etree.tostring(elem, method="text", encoding="unicode"))
运行结果:
这是因为像这样的网页依靠JavaScript来填充内容。著名的Web框架,如AngularJS或React,是这一类别的幕后推手。Python 库,比如requests
,不理解 JavaScript。因此,您将看到不同的结果。如果你想从网上获取的数据就是其中之一,你可以研究JavaScript是如何被调用的,并在你的程序中模仿浏览器的行为。但这可能太乏味了,无法使其正常工作。
另一种方法是要求真正的浏览器读取网页,而不是使用requests
。这就是selenium
可以做的。在使用它之前,我们需要安装库:
pip install selenium
但Selenium只是一个控制浏览器的框架。您需要在计算机上安装浏览器以及将Selenium连接到浏览器的驱动程序。如果您打算使用Chrome,则还需要下载并安装ChromeDriver。您需要将驱动程序chromedriver
放在可执行路径中,以便Selenium可以像普通命令一样调用它。
同样,如果你使用的是Firefox,你需要GeckoDriver。有关设置Selenium的更多详细信息,请参阅其文档。
之后,您可以使用 Python 脚本来控制浏览器行为。例如:
import time
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
# Launch Chrome browser in headless mode
options = webdriver.ChromeOptions()
options.add_argument("headless")
browser = webdriver.Chrome(options=options)
# Load web page
browser.get("https://www.yahoo.com")
# Network transport takes time. Wait until the page is fully loaded
def is_ready(browser):
return browser.execute_script(r"""
return document.readyState === 'complete'
""")
WebDriverWait(browser, 30).until(is_ready)
# Scroll to bottom of the page to trigger JavaScript action
browser.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(1)
WebDriverWait(browser, 30).until(is_ready)
# Search for news headlines and print
elements = browser.find_elements(By.XPATH, "//h3/a[u[@class='StretchedBox']]")
for elem in elements:
print(elem.text)
# Close the browser once finish
browser.close()
上述代码的工作原理如下。我们首先以无外设模式启动浏览器,这意味着我们要求Chrome启动但不显示在屏幕上。如果我们想远程运行脚本,这很重要,因为可能没有任何GUI支持。请注意,每个浏览器的开发方式都不同,因此我们使用的选项语法特定于Chrome。如果我们使用Firefox,代码将是这样的:
options = webdriver.FirefoxOptions()
options.set_headless()
browser = webdriver.Firefox(firefox_options=options)
启动浏览器后,我们会为其提供一个要加载的 URL。但是,由于网络需要时间来交付页面,并且浏览器需要时间来呈现它,因此我们应该等到浏览器准备就绪后再继续下一个操作。我们检测浏览器是否已经使用 JavaScript 完成了渲染。我们让Selenium为我们运行JavaScript代码,并使用该函数告诉我们结果。我们利用Selenium的工具运行它,直到它成功或直到30秒超时。加载页面时,我们滚动到页面底部,以便可以触发JavaScript以加载更多内容。然后,我们无条件地等待一秒钟,以确保浏览器触发了JavaScript,然后等待页面再次准备就绪。之后,我们可以使用XPath(或者使用CSS选择器)提取新闻标题元素。由于浏览器是外部程序,因此我们负责在脚本中关闭它。
使用硒在几个方面与使用库不同。首先,您永远不会直接在Python代码中拥有Web内容。相反,您可以在需要时引用浏览器的内容。因此,该函数返回的Web元素引用外部浏览器内部的对象,因此在完成使用它们之前,我们不得关闭浏览器。其次,所有操作都应基于浏览器交互而不是网络请求。因此,您需要通过模拟键盘和鼠标移动来控制浏览器。但作为回报,您拥有支持JavaScript的全功能浏览器。例如,您可以使用 JavaScript 检查页面上某个元素的大小和位置,只有在呈现 HTML 元素后,您才会知道这些大小和位置。
Selenium框架提供了更多功能,我们可以在这里介绍。它功能强大,但是由于它连接到浏览器,因此使用它比库的要求更高,并且速度要慢得多。通常,这是从网络收集信息的最后手段。
延伸阅读
Python中另一个著名的Web爬行库,我们上面没有介绍过,那就是Scrapy。这就像将requests
库与BeautifulSoup合并为一体一样。网络协议很复杂。有时我们需要管理网络 cookie 或使用 POST 方法为请求提供额外的数据。所有这些都可以通过具有不同函数或额外参数的请求库来完成。以下是一些资源供您深入了解: