Web 抓取 1:抓取表格数据
在这篇文章中,我们将学习如何使用 Python 从 web 上抓取表格数据。简化。
网络搜集是数据收集最重要的概念。在 Python 中, BeautifulSoup 、 Selenium 和 XPath 是可以用来完成网页抓取任务的最重要的工具。
在本文中,我们将重点关注 BeautifulSoup 以及如何使用它从维基百科页面获取 GDP 数据。我们在这个网站上需要的数据是表格的形式。
概念的定义
请看下图,然后我们可以继续定义 HTML 表格的组成部分
图 2
从上图我们可以推断出如下:
标签定义了一个 HTML 表格。
一个 HTML 表格由一个元素和一个或多个、
| 和 | 元素组成。 |
元素定义了表格行,元素定义了表格标题,元素定义了表格单元格。
一个 HTML 表格也可以包括、、、和元素。
我们感兴趣的是检查给定站点的元素(在这种情况下,是我们想要废弃的站点——图 1 的最右边显示了站点的元素)。在大多数电脑中,你访问网站并点击 Ctrl+Shift+I 来检查你想要废弃的页面。
注意:网页的元素通过使用标签上的 class 或 id 选项来识别。id 是唯一的,但类不是。这意味着一个给定的类可以标识多个 web 元素,而一个 id 只能标识一个元素。
现在让我们看看我们想要刮的网站的图像
图 3
从该图中,请注意以下几点:
- 这是统一资源定位符( URL )。我们需要这个。
- 感兴趣对象的标签元素。该对象由类定义,而不是由 id :
class = “wikitable sortable jquery”
定义。注意,tag 元素包含 3 个标识一个表的类(类之间用空格分隔)。除了我们将在这里使用的站点元素的一般引用外,类和id被用作支持使用 CSS 等语言的样式的引用。 - 现场共有 3 张桌子,上图中编号为 3 、 4 和 5 。为了这篇文章,我们将通过如何刮表 3 ,你可以很容易地找出如何做 4 和 5 。
- 当你在页面中悬停以识别你感兴趣的元素时,标有 6 的按钮非常重要。一旦您感兴趣的对象被突出显示,标签元素也将被突出显示。例如,对于我们的案例,标签 2 与标签 3 相匹配。
实际刮削
必需的包: bs4,lxml,pandas 和 requests。
一旦你有了所说的包,我们现在可以通过代码。
在这个代码片段中,我们导入必要的包并解析站点的 HTML 内容。
片段 1
检查代码片段 1 中的站点元素和/或soup
变量后,您会发现图 3 中显示的三个表属于同一个类wikitable
。
片段 2
OUTPUT:
Number of tables on site: 3
为了废弃第一个表,我们只需要从gdp
获得第 0 个索引。下面代码片段中的body
变量包含表格的所有行,包括标题。标题将在body[0]
中,所有其他行将在列表body[1:]
中
片段 3
OUTPUT:
['Rank', 'Country/Territory', 'US$']
标题看起来没错。
接下来,我们需要如下循环其余的行
片段 4
此时,我们在标题上有了标题,在 all_rows 上有了所有其他行,从这里我们可以将这些数据和标题作为 Pandas 数据帧传递
图 4
警告
网络内容受版权保护,在大多数情况下,版权限制内容的复制、分发、传输和采用。当你打算删除这样一个网站时,你可能需要获得内容所有者的明确许可,否则你未经适当同意的删除行为可能会给你带来违约的麻烦。
然而,一些网站在知识共享许可下提供其内容,允许您毫无问题地复制、修改和分发这些内容,尽管一些网站可能对此类内容的商业化提供限制。
底线是在抓取网页之前,你需要确定你有权利这样做。
结论
网络搜集是从网络上收集数据的最重要的工具。要了解更多关于网络抓取的信息,你可以浏览这个包含更多例子的知识库。
加入 https://medium.com/@kiprono_65591/membership的媒体,全面了解媒体上的每个故事。
你也可以在我发文章的时候通过这个链接把文章发到你的邮箱里:【https://medium.com/subscribe/@kiprono_65591
感谢阅读。
网页抓取工具比较—入门所需的一切
一个简单而简洁的美丽的汤,硒,和羊瘙痒的回顾。完成实际的网页抓取例子。
每种工具都有其优点和缺点
我希望您的反馈能让我知道您想阅读什么样的项目——请花几秒钟时间在最后的投票中留下您的答案!谢谢大家!
什么是网页抓取?
网络抓取在当今变得越来越重要。我在这里大胆地说一下,假设你已经听说过“数据是新的石油”这句话。如果我们将网络抓取定义为从几乎任何网站快速收集各种数据的能力,就不难理解为什么会有如此多的企业和工具提供网络抓取服务。
在你开始自己的项目之前,最好记住什么是好的网页抓取礼仪。
极限是天空
你可以使用网络抓取来获取各种各样的数据,如文本、数字、图像、视频、声音等等。并不是所有的网站都提供无缝获取数据的 API,所以我们通常不得不创造性地使用一些好的老式抓取方法。
更复杂的网站结构将需要更先进的抓取技术,但你会惊讶地发现,收集数据和从零开始构建自己的数据集是多么容易。如果您希望自己的数据科学产品组合脱颖而出,这一点尤其有用!
网络抓取三位一体
本文将重点介绍三个不同的 Python 库,对于您开始第一个项目来说,这已经足够了:
- 美汤
- 硒
- 刺儿头
我将介绍它们的主要特性和局限性,并提供一些何时使用其中一种的例子。
如何做出一道漂亮的汤
如果你之前没有经验,美丽的汤可以说是开始你的网络抓取之旅的最佳方式。了解一两件关于 Python 的事情会有所帮助,比如如何遍历列表或字典,以及一点点 pandas 来处理你提取的数据。
它是最友好的用户界面,但这是有代价的——它也是最不灵活的,它不能与使用 JavaScript 的网站一起工作——稍后会详细介绍。
它是如何工作的
网站是用 HTML 代码构建的(至少大部分是这样!).如果你想看到你喜欢的网站背后的代码,在 Chrome 上按 CTRL+U 就可以了(在其他浏览器上找“查看源代码”)。您将看到一个 HTML 文档,其中包含您正在浏览的页面的结构。这些 HTML 代码是我们注入到美丽的汤里的。
该过程的第一步通常是使用请求模块和获取命令来获取 HTML 代码并将其放入变量中。一旦完成,您就可以提取您需要的信息了。
你所需要做的就是煮一些汤 …明白了吗?!
这实际上是一个很好的类比,可以理解下面的例子中发生了什么。一旦您获得了带有页面代码的变量,您就可以将它转换成一个漂亮的 Soup 对象,让您选择想要的任何配料。
在本例中,该对象是 soup 变量,我们将尝试打印页面的标题。在一个普通的 HTML 页面中,主标题总是一个“h1”标签,所以你可以看到对我来说,要汤,要配料是多么容易。
一些成分——我指的是 HTML 标签——将需要一些尝试和错误!每次的逻辑都一样。唯一的区别将是网站的结构如何,以及你如何定义你想要的标签。
多给我看看!
有一个非常好的视频很好地解释了这个问题,如果你决定更深入地研究这个问题,你也可以查看我的个人资料。你会对我们如何从页面中获取特定的标签有一个更好的了解。
如果你试图抓取一个使用 JavaScript 来加载内容的网站,那么到目前为止我们所看到的还不够。为此,我们将不得不使用硒!
用硒赢得你的条纹
当你进入网络抓取的世界时,你最终会到达一个包含 JavaScript 来加载内容的网站。这意味着是时候远离美味的汤,摄入硒了。
如果您对 JavaScript 不熟悉,可以考虑交互式网站,它需要您展开和/或点击对象来显示新信息。预订是一个很好的例子,一个有很多按钮和下拉菜单的网站……你明白了吧!
请务必检查网站是否允许刮擦,并查看 robots.txt 文件。
它是如何工作的
Selenium 是作为自动化测试(web 应用程序和网站)的工具而创建的。简单地说,它就像一个自动化的浏览器窗口。它会打开一个浏览器窗口,让你浏览网站,并随时获取信息。
要使用 Selenium,您需要安装一个模拟浏览器窗口的 web 驱动程序。不同的浏览器有不同的网络驱动程序,但是你应该首先使用你熟悉的浏览器!我的大多数项目都使用了 Chrome 浏览器的网络驱动程序,但是你也可以为 Firefox 和 IE 浏览器安装一个。
对于一个在浏览器窗口没有真正打开的情况下运行 Selenium 的选项,检查一下 PhantomJS 的 webdriver 尽管使用起来有点棘手。
如果您对使用 JavaScript 的网站不确定,请在检查页面时寻找包含 JS 代码的标签(右键单击页面,并选择检查)。
在您完成设置后,是时候刮点东西了!自动化浏览器窗口打开后,您将通过 web 驱动程序与浏览器互动。
多给我看看!
从这里开始,如果你已经和美丽的汤一起工作过,那么接下来应该很容易。您将使用相同的逻辑在包含页面的变量中查找 HTML 标记。你有一个名为“webdriver”的浏览器窗口,而不是一个“汤”。
我试着把上面的代码分成易于阅读的代码块。当您调用 webdriver 时,自动浏览器窗口将会打开。铬合金()。
窗口打开后,您可能会认出获取 somewebsite 页面的 get 方法。它在页面上查找“用户名”字段,并在该字段中发送“您的用户名”。
与“密码”字段相同。然后单击登录。很直接,对吧?诀窍是能够找到标签!
值得一提的是,尽管 Selenium 并不是为 web 抓取而构建的,但它可以成为一个非常有趣的工具!
和 Scrapy 一起拥有互联网
然后我发现了 Scrapy……我看过关于它的视频和教程,但是不得不使用 shell 终端在当时有点吓人。我结束了使用 BS 和硒很长一段时间,直到我决定探索 Scrapy。
一旦我理解了这个概念并释放了我的第一只蜘蛛……嗯,只能说感觉整个世界的可能性刚刚打开。
有一个微小的机会,它甚至会让你觉得像一个黑客,因为你将使用终端,它会在忙的时候打印出很多东西。那肯定是有意义的,对吧?!
它是如何工作的
很快,它的速度快得不可思议。这不是偶然的,因为 Scrapy 是使用 Twisted Python 框架构建的。这个框架给了它异步能力,这使得 Scrapy 没有必要单独等待每个响应。但是这可能比你现在需要知道的还要多!
它也不需要依赖,所以它是一个非常便携的解决方案,你可以在任何安装了 Python 的地方实现。
请放心,您不需要理解它背后的框架就能够使用它。如果您以前见过命令提示符,并且不怕在终端中键入内容,这会很有帮助!
我先解释一下,不需要任何代码,然后我会分享一个简短的视频来展示它的作用。
爬行的蜘蛛
为了创建你的第一个蜘蛛(蜘蛛是你创建来抓取网页的小机器人),你在命令提示符下告诉 Scrapy 生成一个新的蜘蛛。
如果你想知道,我说的 命令提示符 就是你在 Windows 开始菜单上输入“cmd”时得到的那个!
该命令将创建几个**。您运行命令的文件夹中的 py** 文件。使用您最喜欢的 IDE,编辑蜘蛛并给它一些参数,如:
- 你希望蜘蛛从哪个域开始抓取
- 你希望允许蜘蛛抓取哪些域名
- 你希望蜘蛛从它访问的每一页中得到什么信息
我们过滤它跟随的链接,通过指定类似“只允许蜘蛛跟随包含 newspaper.com/economy/".的 url。这样,蜘蛛将只跟随有“经济”文章的链接!
设置好之后,回到 shell 终端,键入命令来运行 spider。它将从你的初始页面开始,并立即获得该页面上的每个链接。如果我们以报纸网站为例,在任何文章页面都会有不同的部分,甚至外部链接(如广告)。
在它跟随的每个链接中,它将试图获取你之前定义的 HTML 标签。这就像是服用了类固醇的美妙汤药,因为它有跟踪页面内部链接的能力。
您还可以在运行 spider 时键入的同一命令中定义将结果导出到哪里。
多给我看看!
不幸的是,在这一点上我没有任何公开的 Scrapy 项目可以炫耀!但是我可以分享我在网上找到的最容易跟随的视频之一。该频道甚至有一个专门为 Scrapy 播放的播放列表,这个家伙做了一个很棒的解释工作。
这是我所需要的吗?
我已经向您简单介绍了每个工具及其核心特性。无论你决定使用哪种工具,一定要阅读或观看一些教程,这样你就能理解如何选择 HTML 标签。不管你选择什么样的库,这都将是你最大的挑战。
所以如果你对 Python 没有太多的经验,我会从选择一个简单的网站开始,比如维基百科,然后开始美丽的汤。
当你对它感到舒适时,可以随意寻找更高级的项目来用 Selenium 解决。我已经留下了一些我自己项目的链接,所以从这里开始吧。
有一天你会醒来并决定你想从一份特定的在线报纸上抓取每一篇文章并进行 NLP 分析。这可能是你需要加载刺儿头的信号!
我希望这篇文章对你开始使用网络抓取有用,或者为你将来的项目提供不同的选择。我真的很想知道你对什么样的话题更感兴趣,所以请花 5 秒钟回答下面的投票!谢谢!
感谢您的阅读!
与往常一样,我欢迎反馈和建设性的批评。
如果想取得联系,可以在这里联系我 或者直接回复下面的文章。
使用 Selenium 和 YOLO 构建计算机视觉数据集的 Web 抓取
此工作流简化了构建数据集的繁重工作
尼古拉斯·皮卡德在 Unsplash 上拍摄的照片
如果我们向几位专家询问计算机视觉项目的关键(特别是如果我们想要一个真正的应用程序),也许最重复的元素(在我看来也是最重要的)就是数据集。当我们问及最艰巨、最费力的任务时,问题就出现了,答案通常也是一样的:数据集。
组成数据集的任务可以概括为三个步骤:
- 捕捉图像。
- 注释图像:为一组类别标记样本。
- 验证:检查标签是否正确。
我们要做的第一件事是求助于可以用于我们任务的最先进的数据集,但问题是我们并不总是能找到我们需要的东西。此时此刻,我们面临着一项艰巨而痛苦的任务。本文展示了如何避免这种手工操作。
使用 Selenium [1】(一个开源的基于 web 的自动化工具) Instagram (间接世界上最大的图像数据库之一)和 YOLO [2】(物体检测中最常用的深度学习算法之一),我们可以自动生成数据集(唯一无法避免的是验证步骤)。为了展示一个简单的例子,我们将生成两个类的简单数据集:cat 和 dog。
我们将设计一个带有 Selenium 的机器人,它将自动访问 Instagram 并在其中移动。此外,我们将使用 YOLO,一个卷积神经网络来检测和排序我们需要的狗和猫。
环境设置
我们将要使用的编程语言是 Python 3.6,它允许我们轻松地使用 Selenium 和 YOLO。
我们将需要以下外部依赖:
- GluonCV :提供计算机视觉中最先进深度学习算法实现的框架。这个工具包为我们提供了大量预先训练好的模型。我们将使用其中的一个[3]。
- 枕 : Python 影像库。
- 【硒】 :它是一个伞式项目,包含一系列工具和库,支持 web 浏览器的自动化。我们将使用该库提供的 Python 模块。
- 请求 :是一个优雅简单的 Python HTTP 库。我们将用它来下载图像。
您可以使用以下命令安装这些依赖项:
pip install mxnet gluoncv Pillow selenium beautifulsoup4 requests
另外,你还需要 ChromeDriver 。这提供了从 Selenium 导航到 web 页面的能力。你要把它复制到usr/local/bin
,瞧!您现在可以从 Selenium 控制您的 Chrome 浏览器了!
物体检测模块
正如我们已经提到的,我们将使用 YoloV3 作为检测模块(顺便说一下, YoloV4 从一个月前就有了)。通过这种方式,我们将尝试寻找和区分猫和狗。
使用 GluonCV 和 MXNet 的 YoloV3 模块
概括地说,YOLO(你只看一次)基本上是一个单一的 CNN,它提供了多个预测(边界框),每个类别都有一个相关的概率。此外,NMS(非最大抑制)用于将多个检测合并为一个检测。我们面对的是一个实时工作的快速神经网络。
使用 YoloV3 模块检测狗和猫。狗:由乔·凯恩在 Unsplash 上的基础图像。猫:基础图片由内森·赖利在 Unsplash 上制作
硒机器人模块
我们将使用 Selenium 对机器人进行编程,使其登录并通过 Instagram,使用检测模块自动下载图像。
正如您在__call__()
方法中看到的,我们可以将代码分为 4 个主要步骤:
- 打开 Instagram 主页。
- 用你的用户名和密码登录 Instagram】。
- 放上你的标签来限制搜索,这里是**#宠物**,因为在这个例子中我们想要狗和猫。
- 还有最重要的一步,滚动和下载图片。
在最后一步中,我们滚动并解析 HTML 以获得下载图像并使用 Pillow 对其进行合成所需的 URL。此时,我们检查图像是否包含一只狗、一只猫,或者什么都不包含,然后将它保存到每个类对应的文件夹中,或者丢弃该图像。
最后,您只需要担心验证样本。尽情享受吧!😉
如果您想轻松地启动这个代码,您可以在这个存储库中找到它。您只需按照自述文件中指示的步骤进行操作。
使用 Selenium 和 Yolo V3 从 Instagram 构建计算机视觉数据集的 Web 抓取。- bsouto/selenium-bot
github.com](https://github.com/bsouto/selenium-bot)
在下面的 GIF 中,你可以看到数据集是如何自动生成的:机器人用用户名和密码访问 Instagram,输入#PETS 标签,滚动,同时只下载狗(底部文件夹)和猫(顶部文件夹)的图像。
操作程序
强调这是一个简单的例子,当然还有很多关于狗和猫的公开数据集。我们可以将我们想要的复杂性添加到我们的生成器中,这是一个概念验证,可能我们的任务不是那么简单,我们需要的数据也不是那么基本。
感谢阅读!
参考
[1]https://selenium-python.readthedocs.io/
https://pjreddie.com/darknet/yolo/
[3]https://gluon-cv . mxnet . io/build/examples _ detection/demo _ yolo . html
使用 Python 和 BeautifulSoup 进行网页抓取
如何从网站中提取数据
图片来自 Pixabay 詹姆斯·奥斯本
网络包含大量的数据。毫无疑问,从中提取你需要的信息的能力是有用的,甚至是必要的。当然,在 Kaggle 这样的地方,仍然有许多数据集可供你下载,但在许多情况下,你不会找到你特定问题所需的确切数据。然而,很有可能你会在网上的某个地方找到你需要的东西,你需要从那里提取。
网络抓取就是这样做的过程,从网页中提取数据。在这篇文章中,我们将看到如何用 python 进行 web 抓取。对于此任务,有几个库可供您使用。这其中,这里我们就用美汤 4 。这个库负责从 HTML 文档中提取数据,而不是下载数据。对于下载网页,我们需要使用另一个库:请求。
所以,我们需要两个包:
- 请求—用于从给定的 URL 下载 HTML 代码
- 美丽的汤——用于从 HTML 字符串中提取数据
安装库
现在,让我们从安装所需的包开始。打开终端窗口并键入:
python -m pip install requests beautifulsoup4
…或者,如果您使用的是 conda 环境:
conda install requests beautifulsoup4
现在,尝试运行以下命令:
**import** requests
**from** bs4 **import** BeautifulSoup
如果您没有得到任何错误,那么软件包安装成功。
使用请求&漂亮的汤来提取数据
从requests
包中,我们将使用get()
函数从给定的 URL 下载网页:
requests.get(url, params**=None**, ******kwargs)
其中参数为:
- url —所需网页的 url
- params —可选字典,查询字符串中要发送的元组或字节的列表
- **kwargs —请求采用的可选参数
这个函数返回一个类型为requests.Response
的对象。在这个对象的属性和方法中,我们最感兴趣的是由目标网页的 HTML 字符串组成的.content
属性。
示例:
html_string **=** requests.get("http://www.example.com").content
在我们获得目标网页的 HTML 之后,我们必须使用BeautifulSoup()
构造函数来解析它,并获得一个BeautifulSoup
对象,我们可以用它来导航文档树并提取我们需要的数据。
soup = BeautifulSoup(markup_string, parser)
其中:
- 标记 _ 字符串 —我们网页的字符串
- 解析器 —由要使用的解析器的名称组成的字符串;这里我们将使用 python 的默认解析器:“html.parser”
注意,我们将第一个参数命名为“markup_string”而不是“html_string ”,因为 BeautifulSoup 也可以用于其他标记语言,不仅仅是 html,但是我们需要指定一个适当的解析器;例如,我们可以通过将“xml”作为解析器来解析 XML。
一个BeautifulSoup
对象有几个方法和属性,我们可以用它们在解析过的文档中导航并从中提取数据。
最常用的方法是.find_all()
:
soup.find_all(name, attrs, recursive, string, limit, ******kwargs)
- 名称 —标记的名称;例如“a”、“div”、“img”
- attrs —带有标签属性的字典;例如
{“class”: “nav”, “href”: “#menuitem”}
- 递归 —布尔;如果为 false,则只考虑直接子代;如果为 true(默认),则在搜索中检查所有子代
- 字符串 —用于在元素内容中搜索字符串
- 限制 —将搜索限制在找到的元素的数量
示例:
soup.find_all("a", attrs**=**{"class": "nav", "data-foo": "value"})
上面的行返回一个列表,其中包含所有具有指定属性的“a”元素。
不能和这个方法的参数或者 python 的关键字混淆的 HTML 属性(比如“class”)可以直接作为函数参数使用,不需要放在attrs
字典里面。HTML 类的属性也可以这样使用,但是不要用class=”…”
写class_=”…”
。
示例:
soup.find_all("a", class_**=**"nav")
因为这个方法是用的最多的一个,所以它有一个捷径:直接调用BeautifulSoup
对象和调用.find_all()
方法效果一样。
示例:
soup("a", class_**=**"nav")
.find()
方法类似于.find_all()
,但它在找到第一个元素后停止搜索;将被返回的元素。它大致相当于.find_all(..., limit=1)
,但它不是返回一个列表,而是返回一个元素。
BeautifulSoup
对象的.contents
属性是一个包含所有子元素的列表。如果当前元素不包含嵌套的 HTML 元素,那么.contents[0]
将只是其中的文本。因此,在我们使用.find_all()
或.find()
方法获得包含我们需要的数据的元素后,我们需要做的就是访问.contents[0]
来获得其中的数据。
示例:
soup **=** BeautifulSoup('''
<div>
<span class="rating">5</span>
<span class="views">100</span>
</div>
''', "html.parser")views **=** soup.find("span", class_**=**"views").contents[0]
如果我们需要的数据不在元素内部,而是作为属性值,那该怎么办?我们可以按如下方式访问元素的属性值:
soup['attr_name']
示例:
soup **=** BeautifulSoup('''
<div>
<img src="./img1.png">
</div>
''', "html.parser")img_source **=** soup.find("img")['src']
Web 抓取示例:获得前 10 个 linux 发行版
现在,让我们用上面的概念来看一个简单的 web 抓取示例。我们将从 DistroWatch 网站上抽取一个列表,列出最受欢迎的 10 个 linux 发行版。distro watch(https://distrowatch.com/)是一个以 linux 发行版和运行在 linux 上的开源软件的新闻为特色的网站。这个网站的右边有一个最流行的 linux 发行版的排名。我们将从这个排名中抽取前 10 名。
首先,我们将下载网页并从中构造一个漂亮的 Soup 对象:
**import** requests
**from** bs4 **import** BeautifulSoup
soup **=** BeautifulSoup(
requests.get("https://distrowatch.com/").content,
"html.parser")
然后,我们需要找出如何在 HTML 代码中识别我们想要的数据。为此,我们将使用 chrome 的开发工具。右键点击网页中的某个地方,然后点击“检查”,或者按“Ctrl+Shift+I”来打开 chrome 的开发者工具。它应该是这样的:
然后,如果你点击开发者工具左上角的小箭头,然后点击网页上的某个元素,你应该在开发者工具窗口中看到与该元素相关的 HTML 片段。之后,您可以使用您在 dev tools 窗口中看到的信息来告诉 beautiful soup 在哪里可以找到该元素。
在我们的例子中,我们可以看到排名被构造成一个 HTML 表,每个发行版名称都在一个带有类“phr2”的 td 元素中。然后在 td 元素里面是一个包含我们想要提取的文本的链接(发行版的名称)。这就是我们在接下来的几行代码中要做的事情:
top_ten_distros **=** []
distro_tds **=** soup("td", class_**=**"phr2", limit**=**10)
**for** td **in** distro_tds:
top_ten_distros.append(td.find("a").contents[0])
这是我们得到的结果:
我希望这些信息对你有用,感谢你的阅读!
这篇文章也贴在我自己的网站这里。随便看看吧!
使用 Python 简化 Web 抓取
了解如何用 Python 抓取网站
Beautiful Soup 是一个 Python 库,便于从网站上抓取信息。在这篇文章中,我想向你展示一些基础知识,让你自己开始抓取网站。我们将一步一步地构建一个 Python Web Scraper 。这比听起来容易。
照片由马库斯·斯皮斯克·temporausch.com从派克斯拍摄
为什么使用 Python Web 抓取?
网络抓取包括通过程序或脚本从网站提取信息。抓取有助于自动提取数据,比我们手动提取信息要快得多。它真的可以节省数小时的手工和繁琐的工作。
例如,如果我们想获得一个包含上传到易贝“无线耳机”类别的所有产品名称的列表,我们可以编写一个 Python 脚本,并使用 Beautiful soup 自动完成这项任务。
如何装美汤?
在终端中运行 pip 命令可以安装 Beautiful Soup。查看官方文档了解更多详情。
pip install beautifulsoup4
在开始编写我们的代码之前,请注意,虽然抓取公共数据并不违法,但我们应该避免每秒向网站发出数百个请求,因为这可能会使网站服务器过载。此外,最好检查你打算删除的网站的条款,以了解它们是否允许删除。
创建我们的 Python 刮刀
好,我们开始吧。我们将抓取 cryptonewsandprices.me 这是一个包含加密新闻库的网站。我们的目标是从站点中提取出版的标题 和日期。
首先,我们应该检查网页的 html 代码,以确定我们希望从站点中提取哪些元素。我们在这篇文章中删除的页面如下所示:
要查看该站点的页面源,右键选择“查看页面源”。然后,我们就可以看到我们将使用美汤解析的站点的 html 源代码 代码。通过查看下面的 html source 的摘录,我们可以看到我们的标题被一个 h5 标签 和类“card-title”所包围。在 Beautiful Soup 及其强大的解析器的帮助下,我们将使用这些标识符来删除信息。
我们需要做的第一件事是导入我们的库请求和 美丽组 。因为我们需要向要废弃的页面发送一个请求,所以我们需要使用请求库。然后,一旦我们得到来自站点的响应,我们将它存储在一个名为" mainContent 的变量中,稍后我们将解析它:
import requests
from bs4 import BeautifulSoupmainContent = requests.get("https://cryptonewsandprices.me/") print(mainContent.text)
我们的问题是,我们用 requests.get 得到的请求不是很用户友好,因此,我们需要把它转换成更容易理解的东西。注意,我们的 mainContent 变量包含了站点的整个 html 代码。
从一个元素中抓取信息
现在让我们摘录一下新闻标题。首先,我们需要将我们在main content变量中的字符串转换成一个 soup Beautiful Soup解析器能够理解(并解析)的“Soup”。可以选择不同的解析器来读取数据。在这篇文章中,我使用“ lxml ”,因为它是最通用和最快的解析器之一。
在下面的代码行中, Soup 包含了我们的 get 请求所指向的整个页面的 html 代码。然后,美汤 lxml 解析器让我们从 html 源代码中提取想要的信息。
Beautiful Soup 提供了一些方法来提取 html 标签、类或网站中其他元素中的文本。既然我们知道每条新闻的标题都使用了一个H5html 标签和类 card-title ,我们可以使用“find”在页面中定位它们,并将值提取到我们的 title 变量中。此外,我们使用“get_text()”只提取 html 标签 h5 和类“card-title”中的文本,而不提取 html 标记。
soup = BeautifulSoup(mainContent.text,'lxml')
title = soup.find('h5', class_='card-title').get_text()
print(title)
太好了,我们已经在页面上打印了第一篇新闻的标题。现在,让我们提取关于这篇文章何时发表的信息。为此,我们首先需要查看一下站点,了解我们可以使用哪个 html 元素来识别“发布在之前”的信息。
如下图所示,我们可以通过“ small 标签和“ text-info 类来识别元素。同样,我们可以使用方法 find 从我们的站点定位并提取对象。
published = soup.find('small', class_='text-info').get_text().strip()print(published)
太好了,现在我们有了发布的信息和最新文章中的图片。
从多个元素中抓取信息
如果能从所有新闻中获得所有标题和发布的信息,而不是只有一个新闻元素,那就更好了。为此,BS 有一个方法叫做 find_all 。其工作原理类似于 查找 😗*
titleall = soup.find_all('h5', class_='card-title')
print(titleall)##printed answer [<h5 class="card-title">Ex-UFC Fighter & Bitcoin Bull Ben Askren: XRP is a Scam </h5>, <h5 class="card-title">Opporty founder calls SEC's ICO lawsuit 'grossly overstated' and 'untruthful' in an open letter </h5>,...]
美汤查找所有方法返回的是一个包含网站包含的所有新闻标题的列表。列表中的每个元素都是一个标题。然而,我们将 html h5 标签作为结果的一部分。
我们之前用来提取文本的 Get_text 方法不适用于列表。因此,为了获得没有 html 标签的每个标题,我们可以遍历列表,然后将 get_text 应用于列表的每次迭代,以将其附加到名为 title_list 的新列表中:
title_list =[]for item in titleall:
individualtitle = item.get_text()
title_list.append(individualtitle) print(title_list)
很好,现在我们得到了没有 html 标签的标题,只有文本。
完成我们的 Python Web Scraper
作为最后一步,如果我们能够提取标题并将其写入一个 csv 文件,那将会非常有趣。为此,我们可以使用 csv 库和 writer 方法:
import csv with open('pythonscraper.csv','w') as csvfile:
writer = csv.writer(csvfile)
for item in title_list:
writer.writerow([item])
就像这样,我们在一个 csv 文件中获得标题新闻列表。你现在可以自己尝试并提取任何其他信息。
如果有什么不清楚的地方,请不要犹豫,在这里写下评论,或者观看下面的 Youtube 视频,在那里我一行一行地浏览脚本。
原载于 2020 年 1 月 28 日 https://codingandfun.com。
一步一步的使用 R 进行网页抓取的指南
让我们用 R 开发一个实时 web 抓取应用程序——比用 Python 简单多了
好的数据集很难找到。这是意料之中的,但没什么好担心的。像网络抓取这样的技术使我们能够随时随地获取数据——如果你知道怎么做的话。今天我们将探索用 R 抓取网络数据有多简单,并通过 R Shiny 漂亮的 GUI 界面来实现。
*那么,什么是网页抓取呢?*简单来说,就是从各种网站收集数据的技术。在以下情况下可以使用它:
- 没有可用于所需分析的数据集
- 没有可用的公共 API
尽管如此,你还是应该经常查看网站关于网络抓取的政策,以及这篇关于网络抓取伦理的文章。在这之后,你应该能够用常识来决定刮擦是否值得。
如果感觉不对,就不要做。
幸运的是,有些网站完全是为了练习抓取网页而制作的。其中之一是books.toscrape.com,顾名思义,它列出了各种流派的书籍:
那么,我们接下来刮那个混蛋,好吗?
行动(或活动、袭击)计划
打开网页,点击任意两个类别(左侧边栏),检查网址。以下是我们的选择:
http://books.toscrape.com/catalogue/category/books/**travel_2**/index.html
http://books.toscrape.com/catalogue/category/books/**mystery_3**/index.html
这些网址有什么共同点?嗯,除了加粗的部分。那就是品类本身。不知道编号是怎么回事,但事实就是如此。每一页都包含一个书单,一本书看起来像这样:
推理类单本书截图
我们的工作是获取一个类别中每本书的信息。这样做需要一点 HTML 知识,但它是一种简单的标记语言,所以我不认为这有什么问题。
我们想刮:
- 标题— h3 > a >标题房产
- 等级— 星级>等级属性
- 价格— 部门产品价格>部门价格颜色>文本
- 可用性— 部门产品价格>部门库存>文本
- 图书 URL—div . image _ container>a>href属性
- 缩略图 URL—div . image _ container>img>src属性
你现在什么都知道了,所以接下来让我们从刮痧开始。
刮书
在 R 中使用rvest
包来执行 web 抓取任务。由于管道操作员的使用和一般行为,它与著名的数据分析包dplyr
非常相似。我们知道如何到达某些元素,但是如何在 R 中实现这个逻辑呢?
下面是一个如何在旅游类别中搜集书名的例子:
library(rvest)
url <- 'http://books.toscrape.com/catalogue/category/books/travel_2/index.html'
titles <- read_html(url) %>%
html_nodes('h3') %>%
html_nodes('a') %>%
html_text()
作者图片
那不是很容易吗?类似地,我们可以刮去其他所有东西。剧本是这样的:
library(rvest)
library(stringr)
titles <- read_html(url) %>%
html_nodes('h3') %>%
html_nodes('a') %>%
html_text()
urls <- read_html(url) %>%
html_nodes('.image_container') %>%
html_nodes('a') %>%
html_attr('href') %>%
str_replace_all('../../../', '/')
imgs <- read_html(url) %>%
html_nodes('.image_container') %>%
html_nodes('img') %>%
html_attr('src') %>%
str_replace_all('../../../../', '/')
ratings <- read_html(url) %>%
html_nodes('p.star-rating') %>%
html_attr('class') %>%
str_replace_all('star-rating ', '')
prices <- read_html(url) %>%
html_nodes('.product_price') %>%
html_nodes('.price_color') %>%
html_text()
availability <- read_html(url) %>%
html_nodes('.product_price') %>%
html_nodes('.instock') %>%
html_text() %>%
str_trim()
厉害!最后一步,让我们将所有这些粘合在一个单独的数据帧中:
scraped <- data.frame(
Title = titles,
URL = urls,
SourceImage = imgs,
Rating = ratings,
Price = prices,
Availability = availability
)
作者图片
你可以在这里结束这篇文章,今天就到此为止,但是我们还可以在此基础上构建一些东西——一个简单易用的 web 应用程序。让我们接下来做那件事。
用于抓取的 Web 应用程序
r 有一个非常棒的 web/dashboard 开发库— Shiny
。它比 Python 中任何类似的东西都容易使用,所以我们将坚持使用它。首先,创建一个新的 R 文件,并将以下代码粘贴到其中:
library(shiny)
library(rvest)
library(stringr)
library(glue)
ui <- fluidPage()
server <- function(input, output) {}
shinyApp(ui=ui, server=server)
这是每个闪亮的应用程序都需要的样板。接下来,让我们来设计 UI 的样式。我们需要:
- 标题——只是在所有内容之上的一个大而粗的文本(可选)
- 侧栏—包含一个下拉菜单,用于选择图书流派
- 中央区域—一旦数据被擦除,显示表格输出
代码如下:
ui <- fluidPage(
column(12, tags$h2('Real-time web scraper with R')),
sidebarPanel(
width=3,
selectInput(
inputId='genreSelect',
label='Genre',
choices=c('Business', 'Classics', 'Fiction', 'Horror', 'Music'),
selected='Business',
)
),
mainPanel(
width=9,
tableOutput('table')
)
)
接下来,我们需要配置服务器功能。它必须将我们精心格式化的输入重新映射到一个 URL 部分(例如,“Business”到“business_35”),并抓取所选流派的数据。我们已经知道如何这样做。下面是服务器函数的代码:
server <- function(input, output) {
output$table <- renderTable({
mappings <- c('Business' = 'business_35', 'Classics' = 'classics_6', 'Fiction' = 'fiction_10',
'Horror' = 'horror_31', 'Music' = 'music_14')
url <- glue('http://books.toscrape.com/catalogue/category/books/', mappings[input$genreSelect], '/index.html')
titles <- read_html(url) %>%
html_nodes('h3') %>%
html_nodes('a') %>%
html_text()
urls <- read_html(url) %>%
html_nodes('.image_container') %>%
html_nodes('a') %>%
html_attr('href') %>%
str_replace_all('../../../', '/')
imgs <- read_html(url) %>%
html_nodes('.image_container') %>%
html_nodes('img') %>%
html_attr('src') %>%
str_replace_all('../../../../', '/')
ratings <- read_html(url) %>%
html_nodes('p.star-rating') %>%
html_attr('class') %>%
str_replace_all('star-rating ', '')
prices <- read_html(url) %>%
html_nodes('.product_price') %>%
html_nodes('.price_color') %>%
html_text()
availability <- read_html(url) %>%
html_nodes('.product_price') %>%
html_nodes('.instock') %>%
html_text() %>%
str_trim()
data.frame(
Title = titles,
URL = urls,
SourceImage = imgs,
Rating = ratings,
Price = prices,
Availability = availability
)
})
}
就这样,我们现在可以运行应用程序并检查行为!
作者 GIF
正是我们想要的——简单,但仍然完全可以理解。让我们在下一部分总结一下。
离别赠言
在短短几分钟内,我们从零到一个工作的网页抓取应用程序。扩展它的选项是无穷无尽的——添加更多的类别、处理视觉效果、包含更多的数据、更好地格式化数据、添加过滤器等等。
我希望你已经设法跟上,你能够看到网络抓取的力量。这是一个虚拟的网站和一个虚拟的例子,但是方法保持不变,与数据源无关。
感谢阅读。
喜欢这篇文章吗?成为 中等会员 继续无限制学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。
[## 通过我的推荐链接加入 Medium-Dario rade ci
作为一个媒体会员,你的会员费的一部分会给你阅读的作家,你可以完全接触到每一个故事…
medium.com](https://medium.com/@radecicdario/membership)
原载于 2020 年 10 月 19 日https://betterdatascience.com。
用 Scrapy 刮网
构建您的第一个网络爬虫
尼古拉斯·皮卡德在 Unsplash 上拍摄的照片
S crapy 是一个流行的用于网页抓取的 Python 框架。出于本教程的目的,我想使用一个我熟悉的网站。我以前做过一个项目,用 Billboard Hot 100 排行榜上的条目作为基本事实,对热门唱片进行分类。我当时使用了一个 python 包装器,它在获取我的数据集时非常有效。然而,我想展示使用 Scrapy 可以很容易地做到这一点。
免责声明:这仅用于培训和教育目的。请熟悉道德规范,因为它们与从网上抓取内容有关。
任何成功的网络抓取项目的第一步是审查要抓取的网站。试着理解发生在引擎盖下的*。在这一步,你的浏览器的网络开发工具将是必不可少的。确定要提取以包含在数据集中的信息。*
我之前在一个项目中使用过 Billboard Hot 100 数据。为了给我的热门歌曲分类器建立真实点击率,我使用 billboard.py 提取每周图表数据。这个包是一个 python 包装器,它使用Beautiful Soup解析来自 Billboard 站点的 html 数据。因为我对这个数据集很熟悉,所以我认为演示如何使用 Scrapy 来构建您的第一个网络爬虫是一个不错的选择。
导航到https://billboard.com/charts/hot-100/。通过右键单击并选择检查或按下option-command-I打开浏览器的 web 开发工具。我在这里禁用 JavaScript,方法是按下shift-command-P*,输入 javascript ,选择 禁用 JavaScript 选项。记得点击刷新按钮或者按下 command-R 来刷新页面。这一步对于决定创建网络爬虫是至关重要的,因为这可以让我看到 Scrapy 看到的页面。*
网络开发工具的使用。
我决定从网站上收集以下数据:
- 图表日期
- 歌名
- 艺人
- 本周排名
- 上周排名
- 峰值位置
- 图表上的周数。
我发现从 1958 年 8 月 4 日到 1961 年 12 月 25 日,图表每周出现和每周一编目。图表是在周六之后公布的。以前图表的 url 遵循 base_url/date_string 的格式。例如,从 1999 年 12 月 25 日这一周的图表将在 https://billboard.com/charts/hot-100/1999–12–25.找到,我们稍后将需要它为我们的网络爬虫创建分页。****
如果你还没有这样做,一定要安装 scrapy。
***$ pip install scrapy***
仍然在命令行中,选择一个您想要工作的目录,创建一个新项目,并创建一个基本的蜘蛛。
***$ cd projects
$ scrapy startproject billboard
$ cd billboard
$ scrapy genspider hot100 billboard.com/charts/hot-100/***
Scrapy 为你的网络爬虫创建了一个具有所有适当层次的新项目。
项目文件夹结构。
在 scrapy shell 命令和 web dev tools 之间,我可以发现如何最好地从 html 中提取我需要的数据。有 100 首歌 出现在每个周图中。它们可以在有序列表元素中找到。通过将这 100 个元素放入一个变量中,我可以遍历每个元素,从每个元素中提取相关信息。我选择使用XPath提取我的数据。如果你愿意,你可以使用 css 选择器 。这个简短的教程假设您对这两者都有一定的了解,所以我不会深入讨论。
**$ scrapy shell billboard.com/charts/hot-100/>>> hit = response.xpath("//li[starts-with(@class, 'chart-list__element')]")>>> len(hit)
100>>> title = hit.xpath(".//span[starts-with(@class, 'chart-element__information__song')]/text()").get()>>> title
'The Scotts'**
既然我已经对我想要抓取的每个项目有了一个好的想法,我将在我选择的文本编辑器中打开我的蜘蛛。我在这个例子中使用了 Visual Studio 代码 ,但是任何代码都可以。我打开项目文件夹,然后打开我创建的名为 hot100.py 的蜘蛛。
我将allowed _ domains从“www.billboard.com/charts”稍微修改为“billboard.com”。我还在start _ requests函数中包含了开始 url,这样我就可以删除 start_urls 变量,因为它不再是必需的。还要注意,我已经包括了 用户代理 而不是允许 Scrapy 使用默认的,这有时会妨碍你的蜘蛛。
蜘蛛的初始设置。
接下来,我想指导蜘蛛如何抓取网站获取信息。我创建了一个变量, hits,,它包含了页面上所有的 100 次点击。然后我创建了一个循环来查找每次命中的变量。
最后,我指示蜘蛛如何前进到下一页。我根据图表的日期创建了一个 date 对象,以便轻松计算前一个图表的日期。该日期随后被转换成格式为 YYYY-mm-dd 的字符串对象。如果您还记得前面的内容,这就是我们需要添加到基本 url 中以获取前一周图表 url 的格式。一个 回调 用于通知蜘蛛回到 解析方法 。
解析方法。
按下 command-J ,在 VS 代码 中打开一个终端窗口。确保您位于预期的目录中。如果没有,请确保相应地更改目录。然后让你的蜘蛛爬出网站!我选择将我的记录保存在一个 中。csv 文件您可以将您的文件存储为任何您想要的结构化格式,例如 。json ,,。xml** 等。总共我蜘蛛爬了大概 4 个小时 30 多万条记录 !**
**$ pwd
$ cd /projects/billboard
$ scrapy crawl -o hot100.csv**
我遇到的挑战之一是超时错误。在 Aminah Nuraini 关于 栈溢出 的解决方案的帮助下,我对我的 settings.py 和middleware . py文件进行了修改,使我的爬虫运行顺畅。如果她的解决方案适合你的个人情况,请随时查看。我不会深入细节,因为它们超出了这个简短教程的范围。**
你有它!你完全有能力创建你的第一个网络爬虫来完成网络抓取的任务。完整的 源代码 可以在 这里找到 。如果你觉得这篇文章很有帮助,请联系我这里和/或 上 的链接。
用 Scrapy 抓取网页:实践理解
网刮系列
与 Scrapy 一起动手
在 第 1 部分 中讨论了使用 Scrapy 的所有理论方面,现在是时候给出一些实际例子了。我将把这些理论方面放到越来越复杂的例子中。有 3 个例子,
- 一个通过从天气站点提取城市天气来演示单个请求和响应的例子
- 一个通过从虚拟在线书店提取图书详细信息来演示多个请求和响应的示例
- 一个演示图像抓取的例子
你可以从我的 GitHub 页面下载这些例子。这是关于使用 Scrapy 和 Selenium 进行网络抓取的 4 部分教程系列的第二部分。其他部分可在以下网址找到
重要提示:
在你尝试抓取任何网站之前,请先通读其 robots.txt 文件。可以像www.google.com/robots.txt一样访问。在那里,你会看到一个允许和不允许抓取谷歌网站的页面列表。您只能访问属于User-agent: *
的页面和Allow:
之后的页面。
示例 1 —通过从天气站点提取城市天气来处理单个请求和响应
我们这个例子的目标是从weather.com提取今天的“Chennai”城市天气预报。提取的数据必须包含温度、空气质量和条件/描述。你可以自由选择你的城市。只需在蜘蛛代码中提供您所在城市的 URL。如前所述,该网站允许抓取数据,前提是抓取延迟不少于 10 秒,也就是说,在从 weather.com 请求另一个 URL 之前,你必须等待至少 10 秒。这个可以在网站的 robots.txt 中找到。
User-agent: *
# Crawl-delay: 10
我用scrapy startproject
命令创建了一个新的 Scrapy 项目,并用
scrapy genspider -t basic weather_spider weather.com
开始编码的第一个任务是遵守网站的政策。为了遵守 weather.com 的爬行延迟政策,我们需要在 scrapy 项目的settings.py
文件中添加下面一行。
DOWNLOAD_DELAY = 10
这一行让我们项目中的蜘蛛在发出新的 URL 请求之前等待 10 秒钟。我们现在可以开始编码我们的蜘蛛。
如前所示,生成了模板代码。我对代码做了一些修改。
import scrapy
import re
from ..items import WeatherItemclass WeatherSpiderSpider(scrapy.Spider):
name = "weather_spider"
allowed_domains = ["weather.com"]def start_requests(self):
# Weather.com URL for Chennai's weather
urls = [
"https://weather.com/en-IN/weather/today/l/bf01d09009561812f3f95abece23d16e123d8c08fd0b8ec7ffc9215c0154913c"
]
for url in urls:
yield scrapy.Request(url=url, callback=self.parse_url)def parse_url(self, response):# Extracting city, temperature, air quality and condition from the response using XPath
city = response.xpath('//h1[contains(@class,"location")]/text()').get()
temp = response.xpath('//span[@data-testid="TemperatureValue"]/text()').get()
air_quality = response.xpath('//span[@data-testid="AirQualityCategory"]/text()').get()
cond = response.xpath('//div[@data-testid="wxPhrase"]/text()').get()temp = re.match(r"(\d+)", temp).group(1) + " C" # Removing the degree symbol and adding C
city = re.match(r"^(.*)(?: Weather)", city).group(1) # Removing 'Weather' from location# Yielding the extracted data as Item object. You may also extract as Dictionary
item = WeatherItem()
item["city"] = city
item["temp"] = temp
item["air_quality"] = air_quality
item["cond"] = cond
yield item
我认为这个例子的代码是不言自明的。然而,我将解释流程。希望你能从 最后一部分 记住 Scrapy 的整体流程图。我希望能够控制请求,所以我使用start_requests()
而不是start_urls
。在start_requests()
中,指定了钦奈天气页面的 URL。如果你想把它改成你喜欢的城市或者增加更多的城市,请随意。对于 URL 列表中的每个 URL,生成一个请求并产生它。所有这些请求都将到达调度程序,调度程序将在引擎请求时分派这些请求。在对应于该请求的网页被下载器下载之后,响应被发送回引擎,引擎将其定向到相应的蜘蛛。在这种情况下,WeatherSpider 接收响应并调用回调函数parse_url()
。在这个函数中,我使用 XPath 从响应中提取所需的数据。
你可能理解到这一部分,代码的下一部分对你来说是新的,因为它还没有被解释。我使用了刺儿头 物品 。这些是定义键值对的 Python 对象。您可以参考此链接了解更多关于物品的信息。如果您不希望使用条目,您可以创建一个字典并放弃它。
可能会出现一个问题,在哪里定义这些所谓的项目。请允许我提醒你一下。在创建一个新项目时,我们看到 Scrapy 正在创建一些文件。记得吗?
weather/
├── scrapy.cfg
└── weather
├── __init__.py
├── items.py
├── middlewares.py
├── pipelines.py
├── __pycache__
├── settings.py
└── spiders
├── WeatherSpider.py
├── __init__.py
└── __pycache__
如果您耐心地沿着这棵树看,您可能会注意到一个名为items.py
的文件。在这个文件中,你需要定义项目对象。
# -*- coding: utf-8 -*-# Define here the models for your scraped items
#
# See documentation in:
# [https://docs.scrapy.org/en/latest/topics/items.html](https://docs.scrapy.org/en/latest/topics/items.html)import scrapyclass WeatherItem(scrapy.Item):
city = scrapy.Field()
temp = scrapy.Field()
air_quality = scrapy.Field()
cond = scrapy.Field()
Scrapy 已经创建了这个类,你需要做的就是定义键值对。在这个例子中,因为我们需要城市名称、温度、空气质量和条件,所以我创建了 4 项。您可以根据项目需要创建任意数量的项目。
当您使用下面的命令运行项目时,将会创建一个包含抓取项目的 JSON 文件。
scrapy crawl weather_spider -o output.json
里面的东西看起来会像,
output.json
------------[
{"city": "Chennai, Tamil Nadu", "temp": "31 C", "air_quality": "Good", "cond": "Cloudy"}
]
万岁!!。你已经成功地执行了一个简单的 Scrapy 项目,处理一个请求和响应。
示例 2 —通过从虚拟在线书店提取图书详细信息来处理多个请求和响应
本例中我们的目标是从网站books.toscrape.com中搜集所有书籍(确切地说是 1000 本)的详细信息。不要担心 robots.txt。这个网站是专门为练习网络抓取而设计和托管的。所以,你是清白的。这个网站是这样设计的,它有 50 页,每页列出 20 本书。您无法从列表页面提取图书详细信息。你必须导航到单本书的网页,以提取所需的细节。这是一个需要抓取多个网页的场景,所以我将使用爬行蜘蛛。像前面的例子一样,我已经创建了一个新项目和一个爬行蜘蛛,使用了scrapy startproject
和
scrapy genspider -t crawl crawl_spider books.toscrape.com
对于这个例子,我将提取书名,它的价格,评级和可用性。这个items.py
文件应该是这样的。
class BookstoscrapeItem(scrapy.Item):
title = scrapy.Field()
price = scrapy.Field()
rating = scrapy.Field()
availability = scrapy.Field()
现在项目所需的一切都准备好了,让我们看看crawl_spider.py
。
class CrawlSpiderSpider(CrawlSpider):
name = "crawl_spider"
allowed_domains = ["books.toscrape.com"]
# start_urls = ["http://books.toscrape.com/"] # when trying to use this, comment start_requests()rules = (Rule(LinkExtractor(allow=r"catalogue/"), callback="parse_books", follow=True),)def start_requests(self):
url = "http://books.toscrape.com/"
yield scrapy.Request(url)def parse_books(self, response):
""" Filtering out pages other than books' pages to avoid getting "NotFound" error.
Because, other pages would not have any 'div' tag with attribute 'class="col-sm-6 product_main"'
"""
if response.xpath('//div[@class="col-sm-6 product_main"]').get() is not None:
title = response.xpath('//div[@class="col-sm-6 product_main"]/h1/text()').get()
price = response.xpath('//div[@class="col-sm-6 product_main"]/p[@class="price_color"]/text()').get()
stock = (
response.xpath('//div[@class="col-sm-6 product_main"]/p[@class="instock availability"]/text()')
.getall()[-1]
.strip()
)
rating = response.xpath('//div[@class="col-sm-6 product_main"]/p[3]/@class').get()# Yielding the extracted data as Item object.
item = BookstoscrapeItem()
item["title"] = title
item["price"] = price
item["rating"] = rating
item["availability"] = stock
yield item
你注意到start_requests()
的变化了吗?为什么我生成一个没有回调的请求?是我在最后一部分说每个请求都必须有相应的回调吗?如果你有这些问题,我赞赏你对细节和批判性推理的关注。向你致敬!!拐弯抹角说够了,让我继续回答你的问题。我没有在初始请求中包含回调,因为rules
中指定了回调和 URL,后续请求将使用该 URL。
流程将从我用http://books.toscrape.com 显式生成一个请求开始。紧随其后的是 LinkExtractor 用模式http://books.toscrape.com/catalogue/.提取链接,爬行蜘蛛开始用 LinkExtractor 用parse_books
作为回调函数创建的所有 URL 生成请求。这些请求被发送到调度程序,当引擎发出请求时,调度程序依次调度请求。像以前一样,通常的流程继续进行,直到调度程序中不再有请求。当您使用 JSON 输出运行这个蜘蛛时,您将获得 1000 本书的详细信息。
scrapy crawl crawl_spider -o crawl_spider_output.json
示例输出如下所示。
[
{
"title": "A Light in the Attic",
"price": "\u00a351.77",
"rating": "star-rating Three",
"availability": "In stock (22 available)"
},
{
"title": "Libertarianism for Beginners",
"price": "\u00a351.33",
"rating": "star-rating Two",
"availability": "In stock (19 available)"
},
...
]#Note: /u00a3 is the unicode representation of £
如前所述,这不是提取所有 1000 本书的细节的唯一方法。一个基本的蜘蛛也可以用来提取精确的细节。我已经用一个基本的蜘蛛程序包含了代码。使用以下命令创建一个基本的蜘蛛。
scrapy genspider -t basic book_spider books.toscrape.com
基本的蜘蛛包含以下代码。
class BookSpiderSpider(scrapy.Spider):
name = "book_spider"
allowed_domains = ["books.toscrape.com"]def start_requests(self):
urls = ["http://books.toscrape.com/"]
for url in urls:
yield scrapy.Request(url=url, callback=self.parse_pages)def parse_pages(self, response):
"""
The purpose of this method is to look for books listing and the link for next page.
- When it sees books listing, it generates requests with individual book's URL with parse_books() as its callback function.
- When it sees a next page URL, it generates a request for the next page by calling itself as the callback function.
"""books = response.xpath("//h3")""" Using response.urljoin() to get individual book page """
"""
for book in books:
book_url = response.urljoin(book.xpath(".//a/@href").get())
yield scrapy.Request(url=book_url, callback=self.parse_books)
"""""" Using response.follow() to get individual book page """
for book in books:
yield response.follow(url=book.xpath(".//a/@href").get(), callback=self.parse_books)""" Using response. urljoin() to get next page """
"""
next_page_url = response.xpath('//li[@class="next"]/a/@href').get()
if next_page_url is not None:
next_page = response.urljoin(next_page_url)
yield scrapy.Request(url=next_page, callback=self.parse_pages)
"""""" Using response.follow() to get next page """
next_page_url = response.xpath('//li[@class="next"]/a/@href').get()
if next_page_url is not None:
yield response.follow(url=next_page_url, callback=self.parse_pages)def parse_books(self, response):
"""
Method to extract book details and yield it as Item object
"""title = response.xpath('//div[@class="col-sm-6 product_main"]/h1/text()').get()
price = response.xpath('//div[@class="col-sm-6 product_main"]/p[@class="price_color"]/text()').get()
stock = (
response.xpath('//div[@class="col-sm-6 product_main"]/p[@class="instock availability"]/text()')
.getall()[-1]
.strip()
)
rating = response.xpath('//div[@class="col-sm-6 product_main"]/p[3]/@class').get()item = BookstoscrapeItem()
item["title"] = title
item["price"] = price
item["rating"] = rating
item["availability"] = stock
yield item
你有没有注意到两只蜘蛛都用了同样的parse_books()
方法?提取图书详细信息的方法是相同的。唯一不同的是,我在基本蜘蛛中用一个专用的长函数parse_pages()
替换了爬行蜘蛛中的rules
。希望这能让你看到爬行蜘蛛和基础蜘蛛的区别。
示例 3 —图像刮擦
在开始这个例子之前,让我们看一下 Scrapy 如何抓取和处理文件和图像的简要概述。要从网页中抓取文件或图像,您需要使用内置管道,具体来说就是FilesPipeline
或ImagesPipeline
,分别用于各自的目的。我将解释使用FilesPipeline
时的典型工作流程。
- 您必须使用蜘蛛抓取一个项目,并将所需文件的 URL 放入一个
file_urls
字段。 - 然后,您返回该项目,该项目将进入项目管道。
- 当项目到达
FilesPipeline
时,file_urls
中的 URL 被发送到调度器,由下载器下载。唯一的区别是这些file_urls
被赋予了更高的优先级,并在处理任何其他请求之前被下载。 - 当文件被下载后,另一个字段
files
将被结果填充。它将包括实际的下载网址,一个相对的路径,在那里存储,其校验和和状态。
FilesPipeline
可用于抓取不同类型的文件(图片、pdf、文本等。).ImagesPipeline
专门用于图像的抓取和处理。除了FilesPipeline
的功能外,它还具有以下功能:
- 将所有下载的图像转换为 JPG 格式和 RGB 模式
- 生成缩略图
- 检查图像宽度/高度,确保它们满足最小限制
此外,文件名也不同。使用ImagesPipeline
时,请用image_urls
和images
代替file_urls
和files
。如果您希望了解更多关于文件和图像处理的信息,您可以随时点击此链接。
我们这个例子的目标是从网站books.toscrape.com上抓取所有书籍的封面图片。为了实现我们的目标,我将重新利用前面例子中的爬行蜘蛛。在开始编写代码之前,有一个重要的步骤需要完成。您需要设置ImagesPipeline
。为此,将以下两行添加到项目文件夹中的settings.py
文件中。
ITEM_PIPELINES = {"scrapy.pipelines.images.ImagesPipeline": 1}
IMAGES_STORE = "path/to/store/images"
现在您已经准备好编码了。因为我重用了爬行蜘蛛,所以爬行蜘蛛的代码不会有太大的不同。唯一的区别是,您需要创建包含images
、image_urls
的 Item 对象,并从 spider 中产生它。
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from ..items import ImagescraperItem
import reclass ImageCrawlSpiderSpider(CrawlSpider):
name = "image_crawl_spider"
allowed_domains = ["books.toscrape.com"]
# start_urls = ["http://books.toscrape.com/"]def start_requests(self):
url = "http://books.toscrape.com/"
yield scrapy.Request(url=url)rules = (Rule(LinkExtractor(allow=r"catalogue/"), callback="parse_image", follow=True),)def parse_image(self, response):
if response.xpath('//div[@class="item active"]/img').get() is not None:
img = response.xpath('//div[@class="item active"]/img/@src').get()"""
Computing the Absolute path of the image file.
"image_urls" require absolute path, not relative path
"""
m = re.match(r"^(?:../../)(.*)$", img).group(1)
url = "http://books.toscrape.com/"
img_url = "".join([url, m])image = ImagescraperItem()
image["image_urls"] = [img_url] # "image_urls" must be a listyield image
items.py
文件看起来像这样。
import scrapyclass ImagescraperItem(scrapy.Item):
images = scrapy.Field()
image_urls = scrapy.Field()
当您使用输出文件运行蜘蛛程序时,蜘蛛程序将抓取http://books.toscrape.com 的所有网页,抓取书籍封面的 URL 并将其作为image_urls
输出,然后将其发送给调度程序,工作流将继续进行,如本例开头所述。
scrapy crawl image_crawl_spider -o output.json
下载的图像将被存储在由IMAGES_STORE
指定的位置,并且output.json
将看起来像这样。
[
{
"image_urls": [
"http://books.toscrape.com/media/cache/ee/cf/eecfe998905e455df12064dba399c075.jpg"
],
"images": [
{
"url": "http://books.toscrape.com/media/cache/ee/cf/eecfe998905e455df12064dba399c075.jpg",
"path": "full/59d0249d6ae2eeb367e72b04740583bc70f81558.jpg",
"checksum": "693caff3d97645e73bd28da8e5974946",
"status": "downloaded"
}
]
},
{
"image_urls": [
"http://books.toscrape.com/media/cache/08/e9/08e94f3731d7d6b760dfbfbc02ca5c62.jpg"
],
"images": [
{
"url": "http://books.toscrape.com/media/cache/08/e9/08e94f3731d7d6b760dfbfbc02ca5c62.jpg",
"path": "full/1c1a130c161d186db9973e70558b6ec221ce7c4e.jpg",
"checksum": "e3953238c2ff7ac507a4bed4485c8622",
"status": "downloaded"
}
]
},
...
]
如果你想抓取其他不同格式的文件,你可以使用FilesPipeline
来代替。我将把这个留给你的好奇心。你可以从这个链接下载这 3 个例子。
避免被禁止
热衷于网络抓取的初学者可能会走极端,以更快的速度抓取网站,这可能会导致他们的 IP 被网站禁止/列入黑名单。一些网站实施了特定的措施来防止机器人爬取它们,这些措施的复杂程度各不相同。
以下是在处理这类网站时需要记住的一些技巧,摘自 Scrapy Common Practices :
- 从浏览器中众所周知的用户代理中轮换你的用户代理(谷歌一下可以得到他们的列表)。
- 禁用 cookie(参见 COOKIES_ENABLED ),因为一些网站可能会使用 cookie 来发现机器人行为。
- 使用下载延迟(2 或更高)。参见 DOWNLOAD_DELAY 设置。
- 如果可能的话,使用谷歌缓存获取页面,而不是直接访问网站
- 使用轮换 IP 池。比如免费的 Tor 项目或者像 ProxyMesh 这样的付费服务。一个开源的选择是 scrapoxy,一个超级代理,你可以附加你自己的代理。
- 使用高度分布式的下载器,在内部绕过禁令,这样你就可以专注于解析干净的页面。这种下载程序的一个例子是 Crawlera
结束语
因为我的目标是让你在读完这篇教程后自信地与 Scrapy 一起工作,所以我克制自己不去深入 Scrapy 的各种错综复杂的方面。但是,我希望我已经向您介绍了与 Scrapy 一起工作的概念和实践,明确区分了基本蜘蛛和爬行蜘蛛。如果你有兴趣游到这个池子的更深的一端,请随意接受通过点击这里可以到达的零碎的官方文件的指导。
在本网刮系列的 下一部分 中,我们将着眼于硒。
在那之前,祝你好运。保持安全和快乐的学习。!
Scrapy 网络抓取:理论理解
网刮系列
Scrapy 入门
在这个知识时代,数据就是一切。它或隐或显地驱动着我们的日常活动。在一个典型的数据科学项目中,数据收集和数据清理约占总工作的 80%。本教程和后续教程将集中在使用 Scrapy 通过网络搜集数据。Scrapy 是一个用于抓取网站和提取结构化数据的应用程序框架,可用于广泛的有用应用程序,如数据挖掘、信息处理或历史档案。
Scrapy 有许多优点,其中一些是:
- 比其他网页抓取工具快 20 倍
- 最适合开发复杂的网络爬虫和抓取工具
- 消耗更少的 RAM 并使用最少的 CPU 资源
尽管有其优势,Scrapy 有一个陡峭的学习曲线和不适合初学者的名声。但是,一旦掌握了,它将成为网络抓取的首选工具。这篇教程是我的一点小小的尝试,让它对初学者更友好。我的目标是让你理解 Scrapy 的工作方式,并让你有信心使用 Python 作为编程语言来使用 Scrapy。要自信地使用 Scrapy,首先必须了解它是如何工作的。
这是关于使用 Scrapy 和 Selenium 进行网络抓取的 4 部分教程系列的第一部分。其他部分可在以下网址找到
数据流概述
杂乱的数据流(来源:https://docs.scrapy.org/en/latest/topics/architecture.html
上图清晰简明地概述了 Scrapy 的工作。让我尽可能清晰简单地解释这些步骤。
- Scrapy 的主要工具蜘蛛向 Scrapy 引擎发送请求。该引擎负责控制框架所有组件之间的数据流,并在特定操作发生时触发事件。这些初始请求启动了抓取过程。
- 引擎将请求发送给调度器,调度器负责收集和调度蜘蛛发出的请求。您可能会问,“为什么需要调度程序?刮难道不是一个直截了当的过程吗?”。这些问题将在下一节中回答。让我们继续工作流程。
- 调度器将请求分派给引擎进行进一步处理。
- 这些请求通过下载器中间件发送到下载器(在图中由引擎和下载器之间的深蓝色条表示)。关于下载器中间件的更多细节,请参考 Scrapy 文档。
- Downloader 然后下载请求的网页,生成响应,并将其发送回引擎。
- 引擎通过蜘蛛中间件将响应从下载器发送到生成请求的相应蜘蛛(在图中由引擎和蜘蛛之间的深蓝色条表示)。你可以在 Scrapy 文档中了解更多关于蜘蛛中间件的信息。
- Spider 通过提取所需的项来处理收到的响应,如果需要,从该响应生成进一步的请求,并将请求发送到引擎。
- 引擎将提取的项目发送到项目管道,以供进一步处理或存储。请点击关于项目管道的链接了解更多信息。引擎还将生成的请求发送给调度程序,并要求将下一个请求发送给下载器。
- 重复上述步骤,直到调度器不再有请求可用。
为什么需要调度程序?
Scrapy 遵循异步处理,即请求进程不等待响应,而是继续执行进一步的任务。一旦响应到达,请求进程就开始操作响应。Scrapy 中的蜘蛛也以同样的方式工作。它们向引擎发出请求,这些请求又被发送到调度程序。可以有任意数量的蜘蛛,每个蜘蛛发送 n 个请求(当然有一个条件。您的硬件的处理能力是极限)。为了处理这些请求,调度程序使用了队列。它不断向队列中添加新请求,并在引擎请求时从队列中调度请求。既然您已经知道需要一个调度器,那么让我来详细说明 Scrapy 是如何处理请求和响应的。
处理单个请求和响应
在 Scrapy 中,提出请求是一个简单的过程。要生成请求,您需要想要从中提取有用数据的网页的 URL。你还需要一个回调函数。当有对请求的响应时,回调函数被调用。这些回调函数使 Scrapy 异步工作。因此,要发出请求,您需要:网页的 URL 和处理响应的回调函数。为了让您更好地理解,我将使用几行代码。典型的 Scrapy 请求如下所示。
scrapy.Request(url="abc.com/page/1", callback=self.parse_page)
这里,url
是要抓取的网页的地址,下载网页后的响应会发送给parse_page()
回调函数,参数是传递的response
,如下图所示。
def parse_page(self, response):
# Do your data extraction processes with the response
您可以使用 XPath 或 CSS 选择器从响应中提取所需的数据。这个提取过程将在后面的部分解释。蜘蛛可以发出任意数量的请求。最需要记住的是 每个请求必须有一个对应的回调函数。
多请求和响应处理
从上一节中,您可能已经理解了如何处理一个请求及其响应。但是,在典型的 web 抓取场景中,会有多个请求。我会尽可能简单地让你理解它的工作原理。让我继续上一节的请求部分。即使这是一个请求,它仍然会被发送到调度程序。根据文档,使用 python 的yield
关键字将请求创建为 iterables 。因此,用 python 的术语来说,请求是使用 python 生成器创建的。产生一个请求会触发引擎将它发送给调度程序,其工作原理前面已经解释过了。类似地,蜘蛛可以使用yield
发出多个请求。下面是一个例子。
def make_requests(self, urls): for url in urls:
yield scrapy.Request(url=url, callback=self.parse_url)
在上面的代码片段中,让我们假设urls
中有 10 个 URL 需要废弃。我们的make_requests()
将向调度程序产生 10 个scrapy.Request()
对象。一旦对其中一个请求有了响应,parse_url()
回调函数就会被调用。现在让我们深入了解响应处理的工作原理。像请求一样,回调函数也必须yield
它从响应中提取的项目。让我用上面代码片段中的回调函数parse_url()
来解释一下。
def parse_url(self, response):
item_name = # extract item name from response using XPath or CSS selector
item_price = # extract item price from response using XPath or CSS selector# yields a dictionary containing item's name and price
yield {
'name': name,
'price': price,
}
提取的项目可以在需要时使用,也可以存储以供持久使用。回调函数可以返回可迭代的请求、字典、项目对象、数据类对象、属性对象或什么都不返回。欲知详情,请点击此处。
Scrapy 的安装和基本操作
我希望你对 Scrapy 的工作原理有一个基本的了解,现在是你开始使用它的时候了。但首先,你需要安装 Scrapy。
安装刮刀
Scrapy 可以通过 anaconda 或 pip 安装。
conda install -c conda-forge scrapy
或者
pip install Scrapy
对于在其他操作系统上安装或任何其他安装查询,请点击此处。
创建新项目
现在你已经安装了 Scrapy,让我们创建一个新项目。Scrapy 提供了一个简单的方法来创建新的项目。导航到您想要创建新的 Scrapy 项目的目录,并键入以下命令。我已经将这个项目命名为tutorial
。您可以自由命名您的项目。
scrapy startproject tutorial
这将创建一个名为tutorial
的文件夹,其中包含以下文件和文件夹。
tutorial/
├── scrapy.cfg
└── tutorial
├── __init__.py
├── items.py
├── middlewares.py
├── pipelines.py
├── __pycache__
├── settings.py
└── spiders
├── __init__.py
└── __pycache__
让我把你的注意力引向spiders
文件夹。这是你创造蜘蛛的地方。要了解每个生成文件的用途,请参考这个链接。
创造蜘蛛
Scrapy 再次提供了一条简单的线来创建蜘蛛。下面显示的语法使用您提供的参数为新的蜘蛛创建了一个模板。
scrapy genspider [-t template] <name> <domain>
有 4 种模板可用,即 4 种蜘蛛类型:basic
、crawl
、csvfeed
、xmlfeed
。在本教程中,我们将重点关注basic
和crawl
蜘蛛。<name>
参数被设置为蜘蛛的名称,而<domain>
参数用于生成allowed_domains
和start_urls
蜘蛛属性。这些<name>
和<domain>
参数都是强制性的。
创建一个基本的蜘蛛
要为域example.com
创建一个基本的 spider,在 spider 项目的根目录下键入以下内容。
scrapy genspider -t basic example_basic_spider example.com
这将在spiders
文件夹中创建一个文件名为example_basic_spider.py
的基本蜘蛛。这个文件的内容应该是这样的。
# -*- coding: utf-8 -*-
import scrapyclass ExampleBasicSpiderSpider(scrapy.Spider):
name = 'example_basic_spider'
allowed_domains = ['example.com']
start_urls = ['http://example.com/']def parse(self, response):
pass
让我解释一下生成的模板的组件。
name
:必须填写。Scrapy 通过蜘蛛的名字来识别它们。allowed_domains
:在抓取网页的过程中,你可能会碰到一个完全指向其他某个网站的 URL。这有助于限制蜘蛛爬行到不必要的域。您可以添加任意数量的想要爬网的域。start_urls
:指定蜘蛛抓取的起始 URL。您可以从多个 URL 开始。parse(self, response)
:这是默认的回调函数。您可以编写代码来操作和提取响应中的数据。
您现在可以自由地用您希望抓取的网页地址修改start_urls
。
等一下!!您可以看到响应,但是请求是如何生成的呢?(你可能还记得上一节的scrapy.Request()
)。当 Scrapy 看到start_urls
时,它会使用start_urls
中的 URL 自动生成scrapy.Request()
,并将parse()
作为回调函数。如果您不希望 Scrapy 自动生成请求,您必须使用start_requests()
功能来生成请求。我将修改为基本蜘蛛生成的相同代码来说明start_requests()
。
# -*- coding: utf-8 -*-
import scrapyclass ExampleBasicSpiderSpider(scrapy.Spider):
"""
Modified Basic Spider to make use of start_requests()
"""
name = 'example_basic_spider'
allowed_domains = ['example.com']def start_requests(self):
urls = ['http://example.com/']
for url in urls:
yield scrapy.Request(url=url, callback=self.parse)def parse(self, response):
pass
请注意,Scrapy 生成的代码只是一个模板。它并不要求你遵守它。你可以随意改变它。你可以定义你的回调函数,但是记住在发出请求的时候使用它。
创建爬行蜘蛛
既然我们已经完成了基本的蜘蛛,让我们继续学习爬行蜘蛛。要创建爬行蜘蛛,请在蜘蛛项目的根目录下键入以下命令。
scrapy genspider -t crawl example_crawl_spider example.com
这将在spiders
文件夹中创建一个文件名为example_crawl_spider.py
的爬行蜘蛛。内容应该是这样的。
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Ruleclass ExampleCrawlSpiderSpider(CrawlSpider):
name = 'example_crawl_spider'
allowed_domains = ['example.com']
start_urls = ['http://example.com/']rules = (
Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
)def parse_item(self, response):
item = {}
#item['domain_id'] = response.xpath('//input[@id="sid"]/@value').get()
#item['name'] = response.xpath('//div[@id="name"]').get()
#item['description'] = response.xpath('//div[@id="description"]').get()
return item
你可能会奇怪看到这么多不同于基本的蜘蛛。爬行蜘蛛是一种特殊的基本蜘蛛,它提供内置的方式从start_urls
中抓取网页,而基本蜘蛛没有这个功能。
你可能已经注意到了主要的区别:rules
。规则定义了抓取网站的特定行为。上述代码中的规则由 3 个参数组成:
LinkExtractor(allow=r'Items/')
:这是爬行蜘蛛最重要的方面。LinkExtractor 提取正在抓取的网页上的所有链接,并且只允许那些遵循由allow
参数给出的模式的链接。在这种情况下,它提取以“Items/”开头的链接(start_urls
+allow
,即“http://example.com/Items/”),并使用回调函数parse_item
为这些提取的链接生成一个请求callback='parse_item'
:为 LinkExtractor 提取的链接生成的请求的回调函数。follow=True
:指定蜘蛛是否应该跟随提取的链接。如果设置为False
,蜘蛛将不会爬行,仅通过start_urls
停止。
如果你想了解更多的规则,你可以点击这里。
***side note:***可以修改基本蜘蛛跨 URL 爬行。这将在本教程的 第二部分 中举例说明。
从响应中提取感兴趣的项目
有两种方法可以从响应(即下载的网页)中提取感兴趣的项目/数据。
- 使用 CSS
- 使用 XPath
响应将保持不变,区别只是从中提取数据的方式不同。在这里的例子中,我将使用 XPath 提取感兴趣的项目。选择 XPath 而不是 CSS 的原因是它们提供了更多的能力和功能。我强烈建议您使用 XPath 浏览一下关于的零碎文档。这里还有一个关于 XPath 的优秀教程。
我将解释示例中使用的几个 XPath 表达式。
示例 XPath 表达式(图片由作者提供)
使用浏览器识别感兴趣项目的标签
打开包含您想要提取的数据的网页。右键点击网页上的任意位置,选择“检查”(Chrome)或“检查元素”(Firefox)。它将打开一个新的面板,显示网页的原始 HTML 版本。当您在 raw 版本上滚动时,网页上相应的元素会高亮显示。向上/向下滚动到要提取的项目,选择该项目,右键单击它,选择“复制”,然后从“新建”菜单中选择“XPath”。如果将复制的 XPath 粘贴到记事本或任何文本编辑器中,您将看到从根到当前项的完整 XPath。现在您已经有了感兴趣的项目的 XPath。您可以使用这个路径作为response.xpath()
的参数并提取值。
使用 XPath 从感兴趣的项目中获取值
使用 XPath 从感兴趣的项目中获取值有两种方法。
get()
- 以字符串的形式返回当前或第一个匹配标签/项目的值
- 示例:
response.xpath('//div[@id="name"]').get()
使用属性“id="name "”返回“div”标记内的值
getall()
- 以列表的形式返回匹配标签/项目的所有值
- 示例:
response.xpath('//div[@id="name"]').getall()
返回一个列表,该列表包含属性为“id="name "”的所有“div”标记的值
追踪蜘蛛
如果你不能运行蜘蛛,所有这些工作都将是浪费,不是吗?不要担心。运行/执行蜘蛛只需要一行命令。你需要做的就是遵循这个语法:scrapy crawl <spider_name>
。
让我试着运行我们刚刚创建的两个示例蜘蛛。
scrapy crawl example_basic_spider
scrapy crawl example_crawl_spider
当您运行蜘蛛时,如果一切正常,没有错误或异常,所有提取的数据将被转储到终端或控制台。要存储这些提取的数据,你需要做的就是给scrapy crawl
命令添加一个选项。
语法:scrapy crawl <spider_name> -o <output_file>
Scrapy 可以以 JSON、CSV、XML 和 Pickle 格式存储输出。Scrapy 还支持更多存储输出的方式。你可以跟随这个链接了解更多。
让我用输出文件重新运行示例蜘蛛。
scrapy crawl example_basic_spider -o output.json
scrapy crawl example_crawl_spider -o output.csv
在典型的真实场景中,您可能需要使用许多蜘蛛来实现特定的目的。当你有很多蜘蛛时,你可能很难记住所有蜘蛛的名字。Scrapy 来救你了。它有一个命令来列出一个项目中所有可用的蜘蛛。
语法:scrapy list
旁注 : Scrapy 有全局命令和项目专用命令。您可以参考这个 链接 来了解这些命令及其功能。
粗糙的外壳
你已经学会了如何创建、提取数据和运行蜘蛛。但是如何得到满足您需要的正确的 XPath 表达式呢?您不能对 XPath 表达式的每一次试验和调整都运行整个项目。为此,Scrapy 为我们提供了一个交互式 shell,您可以在其中摆弄 XPath 表达式,直到对提取的数据感到满意为止。下面是调用交互式 shell 的语法。
刺儿壳:scrapy shell <url to scrape>
一旦 Scrapy 下载了与提供的 URL 相关的网页,您将看到一个带有In [1]:
的新终端提示。您可以开始测试您的 XPath 表达式或 CSS 表达式,无论您喜欢哪个,通过键入您的表达式与response
如下所示。
scrapy shell [https://docs.scrapy.org/en/latest/index.html](https://docs.scrapy.org/en/latest/index.html)...In [1]: response.xpath('//div[@id="scrapy-version-documentation"]/h1/text()').get()
Out[1]: 'Scrapy 2.2 documentation'
您可以在这个交互式 shell 中试验 XPath 或 CSS 表达式。要脱离这个 shell,只需像在 python 交互式 shell 中一样键入exit()
。我建议你首先利用这个 shell 来设计你的表达式,然后开始你的项目。
结束语
至此,本教程的理论部分已经结束。我们先从 中的实际例子开始下一部分 。
在那之前,祝你好运。保持安全和快乐的学习。!
用硒刮网
网刮系列
硒的实际操作
克里斯托夫·高尔在 Unsplash 上拍摄的照片
概观
Selenium 是用于测试 web 应用程序的可移植框架。它是在 Apache License 2.0 下发布的开源软件,可以在 Windows、Linux 和 macOS 上运行。尽管 Selenium 有其主要用途,但它也用作 web 抓取工具。在不深入研究 Selenium 的组件的情况下,我们将关注对 web 抓取有用的单个组件, WebDriver 。Selenium WebDriver 为我们提供了通过编程接口控制 web 浏览器来创建和执行测试用例的能力。
在我们的例子中,我们将使用它从网站上抓取数据。当网站动态显示内容,即使用 JavaScripts 呈现内容时,Selenium 就派上了用场。尽管 Scrapy 是一个强大的网络抓取框架,但它对这些动态网站毫无用处。本教程的目标是让您熟悉 Selenium 并使用它进行一些基本的 web 抓取。
让我们从安装 selenium 和一个 web 驱动程序开始。WebDrivers 支持 7 种编程语言:Python、Java、C#、Ruby、PHP、。Net 和 Perl。本手册中的示例是用 Python 语言编写的。互联网上有其他语言的教程。
这是关于使用 Scrapy 和 Selenium 进行网络抓取的 4 部分教程系列的第 3 部分。其他部分可在以下网址找到
安装 Selenium 和 WebDriver
安装 Selenium
在任何 Linux 操作系统上安装 Selenium 都很容易。只需在终端中执行以下命令,Selenium 就会自动安装。
pip install selenium
安装 web 驱动程序
Selenium 官方有针对 5 网络浏览器的网络驱动。在这里,我们将看到两个最广泛使用的浏览器的 WebDriver 的安装:Chrome 和 Firefox。
为 Chrome 安装 Chrome 驱动程序
首先,我们需要从 Chrome 的官方网站下载 chromedriver 的最新稳定版本。这将是一个 zip 文件。我们需要做的就是提取它,并把它放在可执行路径中。
wget [https://chromedriver.storage.googleapis.com/83.0.4103.39/chromedriver_linux64.zip](https://chromedriver.storage.googleapis.com/83.0.4103.39/chromedriver_linux64.zip)unzip chromedriver_linux64.zipsudo mv chromedriver /usr/local/bin/
为 Firefox 安装 Geckodriver
为 Firefox 安装 geckodriver 更加简单,因为它是由 Firefox 自己维护的。我们所需要做的就是在终端中执行下面的代码行,然后您就可以开始使用 selenium 和 geckodriver 了。
sudo apt install firefox-geckodriver
例子
有两个越来越复杂的例子。第一个将是一个更简单的网页打开和输入文本框和按键。这个例子展示了如何使用程序通过 Selenium 控制网页。第二个是更复杂的 web 抓取示例,包括鼠标滚动、鼠标按钮点击和导航到其他页面。这里的目标是让你有信心开始用 Selenium 进行 web 抓取。
示例 1 —使用 Selenium 登录脸书
让我们使用 Selenium 和 chromedriver 尝试一个简单的自动化任务,作为我们的训练轮练习。为此,我们将尝试登录一个脸书帐户,我们不会执行任何类型的数据搜集。我假设你对使用浏览器的开发工具识别网页中使用的 HTML 标签有所了解。下面是一段 python 代码,它打开一个新的 Chrome 浏览器,打开脸书主页,输入用户名和密码,然后点击登录按钮。
from selenium import webdriver
from selenium.webdriver.common.keys import Keysuser_name = "Your E-mail"
password = "Your Password"# Creating a chromedriver instance
driver = webdriver.Chrome() # For Chrome
# driver = webdriver.Firefox() # For Firefox# Opening facebook homepage
driver.get("https://www.facebook.com")# Identifying email and password textboxes
email = driver.find_element_by_id("email")
passwd = driver.find_element_by_id("pass")# Sending user_name and password to corresponding textboxes
email.send_keys(user_name)
passwd.send_keys(password)# Sending a signal that RETURN key has been pressed
passwd.send_keys(Keys.RETURN)# driver.quit()
执行完这段 python 代码后,你的脸书主页将在一个新的 Chrome 浏览器窗口中打开。让我们研究一下这是如何成为可能的。
- 这一切都始于为您的浏览器创建一个 webdriver 实例。由于我在使用 Chrome,所以我使用了
driver = webdriver.Chrome()
。 - 然后我们用
driver.get("https://www.facebook.com")
打开脸书网页。当 python 遇到driver.get(URL)
时,它会打开一个新的浏览器窗口,打开URL
指定的网页。 - 一旦主页被加载,我们使用它们的 HTML 标签的 id 属性来识别文本框以键入电子邮件和密码。这是使用
driver.find_element_by_id()
完成的。 - 我们使用
send_keys()
发送用于登录脸书的username
和password
值。 - 然后,我们通过使用
send_keys(Keys.RETURN)
发送相应的信号来模拟用户按下回车键的动作。
重要提示:
在程序中创建的任何实例都应该在程序结束时或者在它的目的完成后关闭。因此,每当我们创建一个 webdriver 实例时,必须使用driver.quit()
终止它。如果我们不终止打开的实例,它就会开始耗尽 RAM,这可能会影响机器的性能并降低速度。在上面的例子中,这个终止过程已经被注释掉,以便在浏览器窗口中显示输出。如果终止,浏览器窗口也将关闭,读者将看不到输出。
示例 2 —从 OpenAQ 中收集污染数据
这是一个更复杂的例子。 OpenAQ 是一个收集和分享空气质量数据的非营利组织,这些数据是公开的,可以通过多种方式访问。这一点从该网站的 robots.txt 中可见一斑。
User-agent: *
Disallow:
我们的目标是收集 http://openaq.org 上列出的所有国家的 PM2.5 数据。 PM2.5 是指直径小于 2.5 微米的颗粒物(PM),比人类头发的直径还要小。如果读者有兴趣了解更多关于 PM2.5 的信息,请点击此链接。
选择 Selenium 而不是 Scrapy 的原因是http://openaq.org使用 React JS 来呈现数据。如果是静态网页,Scrapy 会高效地抓取数据。为了收集数据,我们首先需要分析网站,手动导航页面,并记下提取数据所需的用户交互步骤。
了解http://openaq.org的布局
尽量少用网页导航刮总是比较好的。该网站有一个网页https://openaq.org/#/locations可以作为抓取的起点。
来自 https://openaq.org/#/locations的截图突出显示过滤选项
左侧面板上的过滤器位置选项用于过滤出每个国家的 PM2.5 数据。右侧面板上的结果显示了点击显示 PM2.5 和其他数据时打开新页面的卡片。
来自https://openaq.org/#/locations的截图显示了选择国家和 PM2.5 后的结果
包含 PM2.5 数据的示例页面如下所示。从这个页面,我们可以提取 PM2.5 值,位置,城市,国家,日期和时间记录 PM2.5 值使用 XPATH 或 CSS。
来自 https://openaq.org的截图显示点击上一张图片中的位置后的 PM2.5 值
同样,左侧面板可用于过滤和收集包含 PM2.5 数据的所有位置的 URL。以下是我们为收集数据而手动执行的操作。
- 打开 https://openaq.org/#/locations的
- 从左侧面板中,选择/单击国家/地区的复选框。让我们按字母顺序浏览一下这些国家。
- 此外,在左侧面板中,选择/单击复选框 PM2.5。
- 等待卡片装入右侧面板。每张卡片点击后都会打开一个新的网页,显示 PM2.5 和其他数据。
收集 PM2.5 数据所需的步骤
根据执行的手动步骤,从http://openaq.org收集数据分为 3 个步骤。
- 收集 OpenAQ 国家网页上显示的国家名称。这将用于在过滤时选择适当的复选框。
- 从每个国家收集包含 PM2.5 数据的 URL。一些国家包含从不同地点收集的 20 多个 PM2.5 读数。这将需要对网页进行进一步的操作,这将在代码部分进行解释。
- 打开个人网址的网页并提取 PM2.5 数据。
抓取 PM2.5 数据
现在我们有了需要的步骤,让我们开始编码。该示例分为 3 个函数,每个函数执行与上述 3 个步骤相对应的任务。这个例子的 python 代码可以在我的 GitHub 资源库 中找到。
获取 _ 国家()
有一个https://openaq.org/#/countries网页,可以一次显示所有国家,而不是使用 OpenAQ locations 网页。从这个页面提取国家名称更容易。
来自https://openaq.org/#/countries的显示国家列表的截图
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import jsondef get_countries():countries_list = []# driver = webdriver.Chrome() # To open a new browser window and navigate it# Use the headless option to avoid opening a new browser window
options = webdriver.ChromeOptions()
options.add_argument("headless")
desired_capabilities = options.to_capabilities()
driver = webdriver.Chrome(desired_capabilities=desired_capabilities)# Getting webpage with the list of countriesdriver.get("https://openaq.org/#/countries")# Implicit wait
driver.implicitly_wait(10)# Explicit wait
wait = WebDriverWait(driver, 5)
wait.until(EC.presence_of_element_located((By.CLASS_NAME, "card__title")))
countries = driver.find_elements_by_class_name("card__title")
for country in countries:
countries_list.append(country.text)driver.quit()# Write countries_list to json file
with open("countries_list.json", "w") as f:
json.dump(countries_list, f)
让我们了解代码是如何工作的。和往常一样,第一步是实例化 webdriver。这里,不是打开一个新的浏览器窗口,而是将 webdriver 实例化为一个无头窗口。这样,就不会打开新的浏览器窗口,从而减轻了 RAM 的负担。第二步是打开包含国家列表的网页。在上面的代码中使用了等待的概念。
- 隐式等待:当创建时,它是活动的,直到 WebDriver 对象终止。并且对于所有操作都是通用的。它指示 webdriver 在元素加载到网页之前等待一段时间。
- 显式等待:限制于特定 web 元素的智能等待,在本例中,用类名“card__title”标记。一般和
ExpectedConditions
一起使用。
第三步是使用类名为“card__title”的标签提取国家名称。最后,国家名称被写入一个 JSON 文件以便持久保存。下面是 JSON 文件的一瞥。
countries_list.json["Afghanistan", "Algeria", "Andorra", "Antigua and Barbuda", ... ]
get _ urls()
获得国家列表后的下一步是获得记录 PM2.5 数据的每个位置的 URL。为此,我们需要打开 OpenAQ locations 网页,并利用左侧面板过滤出国家和 PM2.5 数据。一旦被过滤,右侧面板将被填入记录 PM2.5 数据的各个位置的卡片。我们提取与这些卡中的每一个对应的 URL,并最终将它们写入一个文件,该文件将在提取 PM2.5 数据的下一步中使用。一些国家有 20 多个地点记录 PM2.5 数据。例如,澳大利亚有 162 个位置,比利时有 69 个位置,中国有 1602 个位置。对于这些国家/地区,位置网页的右侧面板细分为多个页面。这是非常必要的,我们通过这些网页导航,并收集所有地点的网址。下面的代码有一个while TRUE:
循环来执行页面导航的任务。
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
from logzero import logger
import selenium.common.exceptions as exception
import time
import jsondef get_urls():# Load the countries list written by get_countries()
with open("countries_list.json", "r") as f:
countries_list = json.load(f)
# driver = webdriver.Chrome()
# Use headless option to not open a new browser window
options = webdriver.ChromeOptions()
options.add_argument("headless")
desired_capabilities = options.to_capabilities()
driver = webdriver.Chrome(desired_capabilities=desired_capabilities)urls_final = []
for country in countries_list:# Opening locations webpage
driver.get("https://openaq.org/#/locations")
driver.implicitly_wait(5)
urls = []# Scrolling down the country filter till the country is visible
action = ActionChains(driver)
action.move_to_element(driver.find_element_by_xpath("//span[contains(text()," + '"' + country + '"' + ")]"))
action.perform()# Identifying country and PM2.5 checkboxes
country_button = driver.find_element_by_xpath("//label[contains(@for," + '"' + country + '"' + ")]")
values_button = driver.find_element_by_xpath("//span[contains(text(),'PM2.5')]")
# Clicking the checkboxes
country_button.click()
time.sleep(2)
values_button.click()
time.sleep(2)while True:
# Navigating subpages where there are more PM2.5 data. For example, Australia has 162 PM2.5 readings from 162 different locations that are spread across 11 subpages.locations = driver.find_elements_by_xpath("//h1[@class='card__title']/a")for loc in locations:
link = loc.get_attribute("href")
urls.append(link)try:
next_button = driver.find_element_by_xpath("//li[@class='next']")
next_button.click()
except exception.NoSuchElementException:
logger.debug(f"Last page reached for {country}")
breaklogger.info(f"{country} has {len(urls)} PM2.5 URLs")
urls_final.extend(urls)logger.info(f"Total PM2.5 URLs: {len(urls_final)}")
driver.quit()# Write the URLs to a file
with open("urls.json", "w") as f:
json.dump(urls_final, f)
记录运行时间超过 5 分钟的程序的输出总是一个好的习惯。为此,上面的代码使用了logzero
。包含 URL 的输出 JSON 文件如下所示。
urls.json[
"https://openaq.org/#/location/US%20Diplomatic%20Post%3A%20Kabul",
"https://openaq.org/#/location/Kabul",
"https://openaq.org/#/location/US%20Diplomatic%20Post%3A%20Algiers",
...
]
get_pm_data()
从单个位置获取 PM2.5 数据的过程是一个直接的 web 抓取任务,即识别包含数据的 HTML 标签并通过文本处理提取它。在下面提供的代码中也会发生同样的情况。该代码提取国家、城市、位置、PM2.5 值、位置的 URL、记录 PM2.5 值的日期和时间。因为有超过 5000 个 URL 要打开,所以除非安装的 RAM 超过 64GB,否则 RAM 使用会有问题。为了让这个程序在最低 8GB 内存的机器上运行,每 200 个 URL 就终止并重新实例化 webdriver。
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
from logzero import logger
import selenium.common.exceptions as exception
import time
import jsondef get_pm_data():# Load the URLs list written by get_urls()
with open("urls.json", "r") as f:
urls = json.load(f)# Use headless option to not open a new browser window
options = webdriver.ChromeOptions()
options.add_argument("headless")
desired_capabilities = options.to_capabilities()
driver = webdriver.Chrome(desired_capabilities=desired_capabilities)list_data_dict = []
count = 0for i, url in enumerate(urls):
data_dict = {}# Open the webpage corresponding to each URL
driver.get(url)
driver.implicitly_wait(10)
time.sleep(2)try:
# Extract Location and City
loc = driver.find_element_by_xpath("//h1[@class='inpage__title']").text.split("\n")
logger.info(f"loc: {loc}")
location = loc[0]
city_country = loc[1].replace("in ", "", 1).split(",")
city = city_country[0]
country = city_country[1]
data_dict["country"] = country
data_dict["city"] = city
data_dict["location"] = locationpm = driver.find_element_by_xpath("//dt[text()='PM2.5']/following-sibling::dd[1]").textif pm is not None:
# Extract PM2.5 value, Date and Time of recording
split = pm.split("µg/m³")
pm = split[0]
date_time = split[1].replace("at ", "").split(" ")
date_pm = date_time[1]
time_pm = date_time[2]
data_dict["pm25"] = pm
data_dict["url"] = url
data_dict["date"] = date_pm
data_dict["time"] = time_pmlist_data_dict.append(data_dict)
count += 1except exception.NoSuchElementException:
# Logging the info of locations that do not have PM2.5 data for manual checking
logger.error(f"{location} in {city},{country} does not have PM2.5")# Terminating and re-instantiating webdriver every 200 URL to reduce the load on RAM
if (i != 0) and (i % 200 == 0):
driver.quit()
driver = webdriver.Chrome(desired_capabilities=desired_capabilities)
logger.info("Chromedriver restarted")# Write the extracted data into a JSON file
with open("openaq_data.json", "w") as f:
json.dump(list_data_dict, f)logger.info(f"Scraped {count} PM2.5 readings.")
driver.quit()
程序的结果如下所示。该程序从 4114 个单独的位置提取了 PM2.5 值。想象一下打开这些单独的网页并手动提取数据。总的来说,正是这种时候让我们欣赏网络抓取程序或机器人的使用。
openaq_data.json[
{
"country": " Afghanistan",
"city": "Kabul",
"location": "US Diplomatic Post: Kabul",
"pm25": "33",
"url": "https://openaq.org/#/location/US%20Diplomatic%20Post%3A%20Kabul",
"date": "2020/07/31",
"time": "11:00"
},
{
"country": " Algeria",
"city": "Algiers",
"location": "US Diplomatic Post: Algiers",
"pm25": "31",
"url": "https://openaq.org/#/location/US%20Diplomatic%20Post%3A%20Algiers",
"date": "2020/07/31",
"time": "08:30"
},
{
"country": " Australia",
"city": "Adelaide",
"location": "CBD",
"pm25": "9",
"url": "https://openaq.org/#/location/CBD",
"date": "2020/07/31",
"time": "11:00"
},
...
]
结束语
我希望这篇教程已经给了你用 Selenium 开始 web 抓取的信心。示例的完整代码在我的 GitHub 资源库 中。在 下一篇教程 中,我将向你展示如何将硒与 Scrapy 整合。
在那之前,祝你好运。保持安全和快乐的学习。!
网页抓取雅虎财经
从任何公开交易的公司提取财务报表和股票数据
马库斯·斯皮斯克在 Unsplash 上的照片
这个博客的代码可以在我的 GitHub 上找到。
在商界,了解一家公司的财务状况很重要。查看财务报表是了解一家公司经营状况的好方法。
在这篇博客中,我将用 Python 从雅虎财经的数据库中提取任何公司的财务报表。因为 Yahoo Finance 使用 JavaScript,所以我们结合使用了 BeautifulSoup 和 Selenium
导入库
让我们从必要的库开始:
import pandas as pd
from bs4 import BeautifulSoup
import re
from selenium import webdriver
import chromedriver_binary
import stringpd.options.display.float_format = '{:.0f}'.format
设置并运行驱动程序
is_link = '[https://finance.yahoo.com/quote/AAPL/financials?p=AAPL'](https://finance.yahoo.com/quote/AAPL/financials?p=AAPL')driver = webdriver.Chrome()
driver.get(is_link)
html = driver.execute_script('return document.body.innerHTML;')
soup = BeautifulSoup(html,'lxml')
我更喜欢使用 Chrome 作为我的网络浏览器,但也可以随意使用你最熟悉的浏览器(Firefox、Safari 等)。).我也使用苹果公司作为我的示例公司,但是您可以更改链接中的 AAPL 股票代码到另一家公司的股票代码来更改数据。
上述代码将在虚拟浏览器中打开页面,并提取网站正文中的所有数据。由于 Yahoo Finance 是在 JavaScript 上运行的,所以通过这种方法运行代码会提取所有数据并保存,就像它是一个静态网站一样。这对于拉动股价很重要,因为这些是网页上的动态项目,可以定期刷新/更新。
为了提取股票数据,我们从获取股票价格的位置开始。通过将鼠标悬停在股票价格上,我们可以使用 Inspect 工具找到股票价格的确切语法。
将这些信息转移到 Python 中,代码将如下所示:
close_price = [entry.text for entry in soup.find_all('span', {'class':'Trsdu(0.3s) Fw(b) Fz(36px) Mb(-4px) D(ib)'})]
这段代码在所有 HTML 代码中搜索“span”标记,并查找与输入的属性相匹配的 class 属性。幸运的是,这只拉了一个数字,即收盘时的股价。
现在我们有了这个,我们可以继续看财务报表了。
提取财务报表数据
继续抓取,我们搜索页面以找到所有的 div 容器,并深入一点以找到我们想要使用的特性。我发现财务数据的每一行都存储在一个 div 容器中,该容器有一个公共的 class 属性 ‘D(tbr)’ 。在下面的例子中,class 属性中有额外的数据,但是只要第一部分与我们要搜索的内容匹配,它就会提取这些数据。
财务报表上的每一行都存储在一个 div 中
features = soup.find_all('div', class_='D(tbr)')
这将拉出很多看起来杂乱的数据,让我困惑了一段时间。在深入研究提取的数据后,我使用 find 函数来查看我想要的每行数据在哪里。经过大量的试验和错误,我能够生成只提取财务数据的代码:
headers = []
temp_list = []
label_list = []
final = []
index = 0#create headers
for item in features[0].find_all('div', class_='D(ib)'):
headers.append(item.text)#statement contents
while index <= len(features)-1:
#filter for each line of the statement
temp = features[index].find_all('div', class_='D(tbc)')
for line in temp:
#each item adding to a temporary list
temp_list.append(line.text)
#temp_list added to final list
final.append(temp_list)
#clear temp_list
temp_list = []
index+=1df = pd.DataFrame(final[1:])
df.columns = headers
头与数据的其余部分是分开的,因为当把所有数据放在一起时会引起一些问题。在为财务报表中的数据创建一个标题列表和多个列表后,它将所有内容组合在一起生成一个副本!
它看起来就像苹果的损益表(没有格式化),但尽管看起来很完整,下一个问题是页面上的所有数字都保存为字符串。如果我们想对它做任何未来的计算,我们必须把它们都变成整数:
#function to make all values numerical
def convert_to_numeric(column): first_col = [i.replace(',','') for i in column]
second_col = [i.replace('-','') for i in first_col]
final_col = pd.to_numeric(second_col)
return final_col
通过快速 for 循环,我们可以将所有数字字符串转换为整数:
for column in headers[1:]:
df[column] = convert_to_numeric(df[column])final_df = df.fillna('-')
这会将所有带编号的字符串转换为实际数字,对于所有 nan,它会被一个破折号替换。
最终产品看起来非常好,现在可以用于计算!
最终损益表
虽然这只是从苹果公司提取损益表,但如果你在资产负债表或现金流量表上使用雅虎金融的链接,代码应该会提取所有细节,并像上面一样将报表放在一起。
我确信有很多额外的工作可以做,以清理这一点,我很乐意在不久的将来使它看起来更像一个官方声明。在继续这方面的工作的同时,我想对数据进行一些深入研究,以找到基本的财务比率,并检查公司估值、投资分析以及我可以从这些数据中挖掘出的其他趋势/发现。敬请期待!
我希望这段代码有所帮助,如果你有任何反馈或建议,我很乐意听到!
Web 报废、下载 Twitter 数据和使用 Python 进行情感分析
使用数据细分非洲影响者,以推动营销决策。
斯蒂芬·道森在 Unsplash 上拍摄的照片
社交媒体影响者在当前人类互联日益增加的社会中发挥着重要作用;商业组织正在意识到对影响者营销活动的需求(Newberry,2019)。成功的影响者营销活动需要与合适的社交媒体影响者合作。那么,我们如何确定正确的影响者呢?
对于数据科学界来说,只有数据才有答案。让我们进行一个案例研究,以确定在数字营销活动中可以与之合作的合适的非洲影响者。我们将探索从相关网站上删除数据,从 twitter 上为特定用户提取数据,并进行情感分析,以确定合适的影响者进行合作。
Web 报废
该网站有一份预先确定的非洲 100 名有影响力人士的名单,而该网站有一份非洲关键政府官员的名单。要废弃这两个网站,我们首先导入必要的库:
from requests import get
from requests.exceptions import RequestException
from contextlib import closing
from bs4 import BeautifulSoup
import pandas as pd
import re
import os, sysimport fire
接下来,我们通过制作一个HTTP GET request
来定义一个获取网站内容的函数:
def simple_get(url):
try:
with closing(get(url, stream=True)) as resp:
if is_good_response(resp):
return resp.content
else:
return None
except RequestException as e:
log_error('Error during requests to {0} : {1}'.format(url, str(e)))
return None
我们还将定义另一个函数来下载由 URL 指定的页面,并返回一个字符串列表,每个标签元素一个字符串:
def get_elements(url, tag='',search={}, fname=None):
if isinstance(url,str):
response = simple_get(url)
else:
#if already it is a loaded html page
response = urlif response is not None:
html = BeautifulSoup(response, 'html.parser')
res = []
if tag:
for li in html.select(tag):
for name in li.text.split('\n'):
if len(name) > 0:
res.append(name.strip())
if search:
soup = html
r = ''
if 'find' in search.keys():
print('finding',search['find'])
soup = soup.find(**search['find'])
r = soup if 'find_all' in search.keys():
print('finding all of',search['find_all'])
r = soup.find_all(**search['find_all'])
if r:
for x in list(r):
if len(x) > 0:
res.extend(x)
return res
上面的函数使用 BeautifulSoup 库进行原始 HTML 选择和内容提取。选择是基于标签元素或与find
或find_all
一起使用搜索完成的。
我们现在可以调用我们定义的函数并传入我们两个网站的 URL。
res = get_elements('[https://africafreak.com/100-most-influential-twitter-users-in-africa'](https://africafreak.com/100-most-influential-twitter-users-in-africa'), tag = 'h2') url= '[https://www.atlanticcouncil.org/blogs/africasource/african-leaders-respond-to-coronavirus-on-twitter/#east-africa'](https://www.atlanticcouncil.org/blogs/africasource/african-leaders-respond-to-coronavirus-on-twitter/#east-africa')
response = simple_get(url)
res_gov = get_elements(response, search={'find_all':{'class_':'twitter-tweet'}})
这些函数返回网站上符合我们搜索标准的所有内容,因此,我们需要清理数据,只获取 twitter 用户名:
数据清理
在数据清理过程中使用了.split()
和.strip()
方法来去除任何空白或特殊字符。例如,清除res
数据将会:
res_cleaned = []
for element in res:
if re.findall("@", element):
res_cleaned.append(element)
df = pd.DataFrame(res_cleaned)
df1 = df[0].str.split('@', expand = True)influencers = df1[1].tolist()final = []
for influencer in influencers:
influencer = influencer.strip(')')
final.append(influencer)
最后,我们将废弃和清理的数据保存到一个 CSV 文件中:
pd.DataFrame(final, columns = ['Twitter handles']).to_csv('influencers_scraped.csv', index = False, encoding = 'utf-8')
下载 Twitter 数据
就像任何 python 程序都需要的一样,我们从导入所需的库开始。对于这个提取,我们将使用 Tweepy 库来提取 Twitter 数据:
import tweepy
from tweepy.streaming import StreamListener
from tweepy import OAuthHandler
from tweepy import Stream
from tweepy import Cursor
from tweepy import API
接下来,我们初始化变量来存储访问 Twitter API 的用户凭证,验证凭证,并创建到 Twitter 流 API 的连接:
consumer_key = 'YOUR TWITTER API KEY'
consumer_secret = 'YOUR TWITTER API SECRET KEY'
access_token = 'YOUR TWITTER ACCESS TOKEN'
access_token_secret = 'YOUR TWITTER ACCESS TOKEN SECRET'#authentication and connection
auth = OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_token_secret)
auth_api = API(auth)
在成功创建到 twitter 的连接之后,我们继续为用户提取数据,并将数据存储在 CSV 文件中,以便在分析过程中更容易访问。
以下函数接收用户列表和 CSV 文件名作为参数,遍历列表获取每个用户的数据,并将数据保存到 CSV 文件中。
def get_user_info(list, csvfile):
users_info = []
for user in list:
print ('GETTING DATA FOR ' + user)
try:
item = auth_api.get_user(user)
users_info.append([item.name, item.description, item.screen_name, item.created_at, item.statuses_count, item.friends_count, item.followers_count])
except Exception:
pass
print('Done!!')
user_df = (pd.DataFrame(users_info, columns = ["User", "Description", "Handle", "Creation Date", "Tweets", "Following", "Followers"])).to_csv(csvfile, index = False, encoding = 'utf-8')
用户推文中的标签和提及次数也有助于确定个人的影响力水平。因此,我们在 Cursor 对象的帮助下获得用户每条 tweet 中的标签和提及,并再次使用以下函数将数据保存在 CSV 文件中:
def get_tweets(list,csvfile1, csvfile2, csvfile3):
hashtags = []
mentions = []for user in list:
print ("GETTING DATA FOR "+ user)
try:
for status in Cursor(auth_api.user_timeline, id = user).items():
if hasattr(status, "entities"):
entities = status.entities
if "hashtags" in entities:
for ent in entities["hashtags"]:
if ent is not None:
if "text" in ent:
hashtag = ent["text"]
if hashtag is not None:
hashtags.append(hashtag)
if "user_mentions" in entities:
for ent in entities["user_mentions"]:
if ent is not None:
if "screen_name" in ent:
name = ent["screen_name"]
if name is not None:
mentions.append([user, name])
except Exception:
pass
print("Done!")
hashtags_df = (pd.DataFrame(hashtags, columns = ["hashtags"])).to_csv(csvfile1, index = False, encoding = "utf-8")
mentions_df = (pd.DataFrame(mentions, columns = ["mentions"])).to_csv(csvfile2, index = False, encoding = "utf-8")
情感分析
既然我们已经挖掘了所有必要的数据,我们可以继续进行分析,以确定影响者的排名和最常用的标签。用于排名的公式基于“衡量 Twitter 中的用户影响力:百万追随者谬误”的论文。
根据 Gummadi、Benevenuto、Haddadi 和 Cha (2010)的研究,一个人的影响力可以通过以下方面来衡量:
- 深度影响力——个人的受众,
- 转发影响力——个人产生有价值内容的能力,以及
- 提及影响力 —个人进行引人入胜的谈话的能力。
这三个度量可以分别计算为到达分数、流行度分数和相关性分数。
reach_score = item.followers_count - item.friends_count
popularity_score = retweet_count + favorites_count
relevance_score = mentions_count
另一方面,最常用的 hashtags 是使用 Counter 模块计算的:
unique_hashtags = []
for item, count in Counter(hashtags).most_common(5):
unique_hashtags.append([item, count])
最常见的标签数据可用于理解标签的分布以及不同影响者如何使用标签。下面的代码输出一个包含这些信息的条形图。
df = pd.DataFrame(pd.read_csv('hashtags_data.csv'))unique_hashtags = ['...',...,'...']
# selecting rows based on condition
hashtags_grouping_data = df[df['hashtags'].isin(unique_hashtags)]grouped = hashtags_grouping_data.groupby(['hashtags', 'user_type']).agg({'user_type': ['count']})grouped.columns = ['count']
grouped = grouped.reset_index()bp = sns.barplot(x = 'hashtags', y = 'count', hue = 'user_type', data = grouped)#Position the legend out the graph
bp.legend(bbox_to_anchor=(1.02, 1),
loc=2,
borderaxespad=0.0);
bp.set(title='Bar plot for Influencers and Top Government Officials by hashtag', xlabel='Hashtags', ylabel='Count')
由上述代码构建的示例条形图如下所示:
作者图片
结论
影响者营销已经成为新的营销利基,商业组织正在寻找合适的社交媒体影响者,以便与他们合作开展有效的营销活动。利用数据科学,我们可以帮助推动基于事实数据的营销决策。它就像从相关网站上删除数据,从 Twitter 等社交媒体平台下载用户数据,并对数据进行情感分析,以得出商业组织可以更好理解的视觉效果一样系统。帖子概述了该过程中使用的主要代码,完整代码可在此处找到。
参考
[1]: C .米扬,h .哈默德,b .法布里西奥,g .克里希纳,测量推特中的用户影响力:百万追随者谬误 (2010),【http://twitter.mpi-sws.org/icwsm2010_fallacy.pdf】T4
[2]: N. Christina,影响者营销指南:如何与社交媒体影响者合作 (2019),https://blog.hootsuite.com/influencer-marketing/
实时机器学习端点的 Web 服务与流
Playtika 如何确定大规模交付实时多媒体流端点的最佳架构
作者图片
机器学习(ML)已经成为行业中发展最快的趋势之一。许多公司花费时间和金钱进行营销活动,以展示他们如何使用 ML 进行业务自动化和洞察,从而创造出击败竞争对手的成功产品。当然,很少有公司真正生产和研究塑造用户体验的机器学习模型(并且是实时的)。
Playtika 的人工智能研究是数据科学研究的所有魔力所在,以便产生实时游戏决策,为我们的用户提供更好的游戏体验。Playtika 是一家游戏娱乐公司,为世界各地的观众提供各种基于质量的游戏,以及不断变化并专门为每位玩家量身定制的原创内容。Playtika 利用海量数据,通过根据游戏中的动作定制用户体验,重塑游戏格局。凭借超过 3000 万的月活跃用户(MAU)、100 亿次每日事件和超过 9TB 的每日处理数据,Playtika 能够为其科学家提供所需的数据,以根据用户的游戏行为为其用户创建不断变化和自适应的游戏环境。
随着使用机器学习模型来满足业务需求的需求增加,大脑工程团队发现自己需要一个强大的基础设施,能够水平扩展,并能够处理突发事件、峰值和快速创建新的 ML 管道。我们对如何处理我们的实验、扩展和部署进行了大量思考,并找到了一家新的本土(以色列)创业公司,由经验丰富的数据科学家创建,满足了我们的需求:满足 cn vrg . iocn vrg . io 是一款人工智能操作系统,旨在组织数据科学项目的每个阶段,包括研究、信息收集、代码编写和大规模模型优化。 cnvrg.io 统一了我们的 ML 工作流程,并提供了 MLOps 解决方案,以便我们的团队可以更加专注于交付高影响力的机器学习模型,并为我们的业务和用户创造价值。
使用 cnvrg.io 的 MLOps 解决方案,我们能够分离我们的数据科学家和 ML 数据工程师之间的大部分工作。cnvrg.io 能够持续编写、训练和部署机器学习模型到各种阶段(即训练、试运行、生产),只需点击一个按钮,就能满足我们从研究到生产的流程和需求。它在一个用户友好的界面中拥有 Amazon SageMaker、Mlflow、Kubeflow 等世界知名框架提供的所有工具,其功能包括数据版本控制和管理 t、实验跟踪、模型管理、模型监控和部署。一旦我们的模型被训练、管理和验证,它们就被推到我们的 Kubernetes 集群之上进行生产,准备接收 RESTful API 对预测和推理的请求。能够轻松地部署 ML 模型非常好,但是我们发现我们的模型使用了各种类型的流程。一些模型由批处理触发,其中每天一次,一个预定的气流任务调用一个火花过程,该火花过程将收集数百万玩家的特征,然后发送请求给模型进行预测。其他模型有 Kafka 消费者过程,由游戏中的事件触发,然后调用该模型进行推理。我们已经看到,随着 Playtika 的 DAU 增长,业务将更多的玩家流量推向我们的模型,我们无法总是预测我们的峰值,我们突然需要处理请求失败、批量分区等问题。我们认识到,正如软件世界从 RESTful 服务转向流 API 一样,ML 的世界也应该如此。
作者图片
遇到的问题
在更高的层面上,我们在 ML 管道中遇到的 REST APIs 的主要问题可以归纳为以下三类:
- 服务用尽
- 客户端饥饿
- 处理失败和重试
- 批量分区的大容量性能调整
由于我们的批处理过程收集了数百万用户和数百(如果不是数千)个功能,我们无法通过 REST API 向机器学习模型发送一个批量请求,并期望它在合理的时间内返回(不到几分钟——个位数)。这时,我们的数据工程师决定,最好是将这一批分成大约 1000 个播放器的批量(在对批量吞吐量进行一些调整之后)。然而,串行发送 1000 个批量数据会花费很长时间,所以我们允许并行处理这个过程,因此有多个客户端线程发送请求。但是,这反过来又导致了模型的耗尽,这些模型向发出呼叫的客户端引入了超时,并导致了重试机制以及基于死信队列的统计分析的需要。客户端重试产生了更多的流量和混乱,导致服务耗尽和客户端饥饿,我们喜欢称之为“自我拒绝服务”。
这让我们开始研究断路器和速率限制,甚至改变架构。那些广泛使用微服务的人可能听说过隔板模式,这是一种容许失败的应用程序设计。在隔板架构中,应用程序的元素被隔离到池中,这样,如果一个元素出现故障,其他元素将继续工作。它以船体的分段隔板(舱壁)命名。如果船体受损,只有受损部分会进水,从而防止船只沉没。然而,按用户组或请求者 ID 划分模型在设计和部署中是一个很大的开销,因此看起来是一个与我们的问题无关的解决方案。
思维能力
所以,我们决定最好回到我们系统架构的起源和我们游戏架构的关键概念。Playtika 的游戏有一个基于事件驱动的服务后端。一些工作室(游戏)部署了数百个微服务,它们之间的通信主要是通过 Kafka(我们的消息总线)来完成的。这反过来创建了一个异步的(因此最终是一致的)、容错的、可重复的和高度可扩展的系统。我们也知道,我们的游戏和机器学习模型从来不会使用 RESTful APIs 直接通信。背后的基本推理是,我们成功的最重要参数是主游戏玩法和用户体验不会受到影响。所有额外的“最好拥有”功能和见解都很重要,但我们的主要目标是让用户在继续玩游戏的同时保持兴奋、参与和娱乐。因此,来自游戏的所有事件都通过公共 Kafka 主题到达我们的数据平台,这些主题被传输到我们系统的不同层,包括我们的数据湖和流处理管道,这些管道对数据进行装饰和标准化,以允许 Playtika Brain 等消费者运行机器学习模型。而且这些模型的结果通过同样的手段——卡夫卡主题,以治疗(游戏动作中)或者用户状态预测的形式推回到游戏中。由于 Playtika 建立的这种事件驱动的生态系统,我们自己引入流预测管道才有意义。
这时,我们与 cnvrg.io 进行了一次通话,并讨论了不仅使用 REST API 打包我们的模型,而且使用 Kafka Streams 进行传入请求和传出推理的可能性。对于那些不熟悉 Kafka Streams 的人,Kafka Streams 是一个用于构建应用程序和微服务的客户端库,其中的输入和输出数据存储在 Kafka 集群中。它解决了我们在使用批处理和 REST API 方法时遇到的许多难题。
- 毫秒级延迟的一次一个事件处理(非微批处理)
- 一次加工
- 分布式处理和容错以及快速故障转移
- 重新处理功能,以便您可以在代码更改时重新计算输出
除了 Kafka Streams 解决的强大功能之外,流处理本身还允许我们获得更好的模型计算性能。由于 Playtika 是事件驱动的,所以我们通常看吞吐量而不是延迟。我们承认延迟很重要,尤其是在我们的前端服务中,但我们的许多后端服务明确地关注它们可以处理的消息数量,而不是单个请求的延迟。转向流处理使我们能够将 Kubernetes 豆荚发挥到最佳配置。我们确保部署每个 Pod,使得在该节点上运行的应用程序线程的数量最多为 N-1,其中 N 是该节点上的 CPU 数量。由于我们的模型执行很少的 I/O,并且仅受计算限制,因此我们希望尽可能少的上下文切换,并希望确保每个线程运行并利用单个 CPU。你问为什么是 N-1 而不是 N?嗯,我们确实希望我们心爱的操作系统有自己的处理单元。因此,通过使用流处理,我们能够提高底层 CPU 的使用率和性能,从而实现更大的吞吐量。
测试和概念验证
带着所有这些想法,我们决定对它们进行试验。这正是我们与 cnvrg.io 的对话变得有趣的时候,并且在流媒体功能处于开发阶段时,我们与 cnvrg.io 进行了一次快速概念验证,结果如下。
作者图片
我们采用了一个经过训练的 IMDB 情感分析模型,并以两种形式部署它。第一个是使用包含 Nginx 和 Flask 的 Pod,而第二个是使用包含 Kafka Streams 应用程序的 Pod。
作者图片
部署架构
实验是在 AWS 平台上进行的。每个模型都运行在自己专用的 EC2 m5 .大型实例上(2 个 CPU,8GB RAM)。
REST API
作者图片
REST 服务(模型)是部署到驻留在 nginx 后面的 uWSGI 服务器的 Flask 微服务。
卡夫卡溪流
作者图片
流微服务是一个守护程序服务,它使用 Faust(python 流处理库)来消费来自 Kafka 主题的数据,并将结果输出到输出主题。
结果
实际的结果参数可以在附录部分看到,在那里可以找到所有的图表。
吞吐量
从下面的结果中,我们发现通过使用 Kafka Streams,与 RESTful APIs 相比,我们能够将模型吞吐量提高 50%,平均提高 30%。应该注意的是,在考虑吞吐量时,我们应该考虑成功的吞吐量。这是发送的成功请求数(返回 HTTP 状态代码 200 的请求数)除以测试运行的总秒数。由于计算中存在失败(大多是由 HTTP 超时引起的),我们不仅“丢失”了发送的请求,我们可能还需要将它们作为重试来重放,并在模型上创建更多的流量。
另一个观察结果是,Kafka Streams 微服务吞吐量不会随着时间的推移而出现很大差异。在 web 服务中,差距为 0.5 万亿次,而在 web 服务中,差距超过了 5 万亿次。吞吐量随时间的一致性使我们能够评估和规划模型的性能和可扩展性。
错误
由于 Kafka Streams 是容错的,并且只进行一次处理,所以我们的实验中没有错误。这并不意味着 Kafka 没有问题,可以失败,但是,在其基础上,它被设计为高度可用和传输数十亿条记录。它能够处理突发,因此错误的数量总是最小的。此外,Kafka Streams 被设计为只允许一次处理,因此我们知道我们不会导致消息重复。
当观察 REST API 结果时(如图 1 所示),我们看到错误率在第一分钟后增加,并根据模型实例的负载持续发散。客户端正在耗尽 web 服务,这导致模型吞吐量降低,从而导致更大的延迟和错误。可以清楚地看到,延迟和错误率彼此正相关,而吞吐量与两者负相关。
结论
在对我们的模型运行时进行深入分析后,我们能够看到,迁移到 Kafka Streams 等流技术不仅增加了我们在高峰时段的模型吞吐量,还使我们的容量规划更具确定性和可预测性。我们现在能够处理高峰和故障,而不必创建卫星系统或编写代码来处理故障和边缘情况。我们得出了一条经验法则,即“ 当计算独立且离散 时,所有异步预测都可以转化为流”。这意味着当对来自不同玩家(实体)的事件的预测不受影响(独立)并且同一玩家(实体)的事件不需要分组在一起(离散)时,可以应用流式技术。这个定义不适用的一个用例是聚类问题,其中事件必须被分组在一起,任何类型的重新分组都会影响标签(目标结果)。
REST API
图表-1
作者图片
表-1
作者图片
图表-2
作者图片
表-2
作者图片
流结果
图形-3
作者图片
表-3
作者图片