您可以在此处 找到我们将要编写脚本的完整可运行示例。
什么是网络爬取(Web Scraping)?
好,那么网络爬取 到底是什么?顾名思义,它是一种从网页“抓取”或提取数据的方法。您在浏览器中能看到的任何内容(包括本教程)都可以被抓取到本地硬盘上。
网络爬取有许多用途。对于任何数据分析,第一步都是数据获取。互联网是人类历史与知识的巨大存储库,您可以提取所需的任意信息,并据此开展您的工作。
在本教程中,我们将使用 Python 和 BeautifulSoup 4 包从一个 subreddit 获取信息。我们关注的是datascience 子版块 。我们希望获取该子版块的前 1000 篇帖子并将其导出到 CSV 文件中。我们想知道是谁发的帖,以及它的点赞数和评论数。
本教程将涵盖以下内容:
使用 requests 获取网页
在浏览器中分析网页以查找信息
使用 BeautifulSoup 从原始 HTML 中提取信息
注意:我们将使用 Reddit 网站的旧版本,因为它更轻量,加载更快,对您的机器负担更小。
前置要求
本教程假设您了解以下内容:
在您的计算机上运行 Python 脚本
具备 HTML 结构的基础知识
您可以在 DataCamp 的Python 初学者课程 中学习上述技能。话虽如此,这里涉及的概念非常基础,即使只掌握少量 Python 知识也能读懂。
既然准备工作就绪,我们可以进入构建网络爬虫的第一步了。事实上,这也是编写任何 Python 脚本的第一步:导入。
在我们的爬虫中,将使用以下包:
您可以当然使用 pip 安装这些包,如下所示:
pip install package_name
技术支持
下载完这些包后,请将它们导入到您的代码中。
import requests
import csv
import time
from bs4 import BeautifulSoup4
技术支持
我们将使用 Python 内置的 csv 模块将结果写入 CSV 文件。您或许注意到上面代码片段中有一点“古怪”。也就是我们下载了一个名为 beautifulsoup4 的包,却从一个名为 bs4 的模块中导入。这在 Python 中是合法的,尽管通常不被推崇,但并不“违法”。
关于平台政策的重要说明
在使用我接下来展示的爬取技巧之前,请务必查看 Reddit 或您计划抓取的任何其他网站的最新服务条款。网站政策会随着时间变化。2024 年 9 月的Reddit 用户协议 禁止未经事先书面同意的抓取(见第 7 条)。在继续之前,请务必确保您遵守该网站的最新使用指南。
爬取 Reddit 的第一步
我们的环境已经就绪。接下来,我们需要目标网页的 URL。在本教程中,我们使用 Reddit 的“datascience”子版块。在开始写脚本之前,我们需要做一些“实地考察”。打开浏览器,进入相关子版块 。
请注意,我们将使用该子版块的旧版页面进行爬取。新版页面将一些关键信息隐藏在网页内部。虽然同样可以从新版网站中提取这些信息,但出于简化考虑,我们将使用信息更直观的旧版页面。
打开链接后,您会面对信息量的“洪流”。在这些内容里我们需要什么?通过梳理所有链接,您会发现 Reddit 帖子分为两类:
站内链接指向 Reddit 自身的内容,站外链接则相反。之所以重要,是因为我们只想要站内链接。也就是包含用户撰写文本的帖子,而不是仅指向其他网站的链接。
那么,知道了目标,如何提取呢?如果您查看帖子的标题,会看到其后跟着方括号中的一些文本。我们感兴趣的帖子后面跟着“(self.datascience)”。从字面上,我们可以推断“self”指代 Reddit 根目录,“.datascience”指代该子版块。
很好,这样我们就能区分站内与站外帖子了。接下来需要在 DOM 结构中定位它。是否可以通过在 DOM 结构中搜索标签、class 或 id 来找到这些标识符?当然可以!
在浏览器中,右键任意一个“self.datascience”链接,点击“检查元素”。大多数现代浏览器都有这个强大的工具,允许开发者动态遍历网页源代码中有意义的部分。如果您使用 Safari,请确保在 Safari 偏好设置中启用了开发者工具。您会看到一个面板,它已经定位到负责显示“(self.datascience)”标识符的源码部分。
在该面板中,您会看到这个标识符实际上是由一个锚点标签加载的。
< a href = " /r/datascience" > self.datascience</ a>
技术支持
但它被一个 span 标签包裹着。
< span class = " domain" >
"("
< a href = " /r/datascience" > self.datascience</ a>
")"
</ span>
技术支持
这个 span 标签包含了我们在页面上看到的文本,即“(self.datascience)”。如您所见,它带有“domain”这个 class。您可以检查其他链接是否遵循相同格式,结果是——确实如此。
使用 BeautifulSoup 获取页面
我们知道页面上要找什么,这很好,但如何用 Python 读取页面内容?其实它的工作方式与人类通过浏览器读取页面内容非常相似。
首先,我们需要使用“requests”库请求网页。
url = "https://old.reddit.com/r/datascience"
headers = { 'User-Agent' : 'Mozilla/5.0' }
page = requests. get( url, headers= headers)
技术支持
现在我们有了一个 Response 对象,其中包含网页的原始文本。就目前而言,我们还无法对其做什么。我们手里只是一个包含整个 HTML 源代码的巨大字符串。要理解它,我们需要使用 BeautifulSoup 4。
请求头允许我们模拟浏览器访问。由于对机器人的响应与对浏览器的响应不同,而我们的参考对象是浏览器,因此最好获取浏览器的响应。
BeautifulSoup 允许我们通过搜索任意组合的 class、id 或标签名来查找特定标签。这是通过创建一棵语法树来实现的,不过其细节与我们的目标无关(也超出本教程范围)。
让我们来创建这棵语法树。
soup = BeautifulSoup( page. text, 'html.parser' )
技术支持
soup 就是一个 BeautifulSoup 对象,由一段原始源代码字符串创建。请注意我们需要指定 HTML 解析器。这是因为 BeautifulSoup 也可以从 XML 创建 soup。
我们知道要找哪些标签(class 为“domain”的 span 标签),也有了 soup。接下来就是遍历 soup 并找到这些标签的所有实例。用 BeautifulSoup 来做这件事简单得令人发笑。
domains = soup. find_all( "span" , class_= "domain" )
技术支持
我们刚刚做了什么?我们在 soup 对象上调用了“find_all”方法,它会在自身中查找与传入参数匹配的锚点标签作为第二个参数。我们这里只传入了一个限制条件(class 必须为“domain”),但也可以与其他条件结合,或仅使用 id。
我们使用“class_=”是因为“class”是 Python 的保留关键字,用于定义类等。
如果您想传入多个参数,只需将第二个参数写成包含所需参数的字典,如下所示:
soup. find_all( "span" , { "class" : "domain" , "height" , "100px" } )
技术支持
我们已经把所有 span 标签放进了“domains”列表,但我们想要的是“(self.datascience)”这样的域名。
for domain in domains:
if domain != "(self.datascience)" :
continue
print ( domain. text)
技术支持
对,现在您应该能看到页面上帖子类型的列表,并排除了不是“(self.datascience)”的那些。但我们基本上已经得到了想要的一切。我们现在持有所有站内帖子的引用。
打印几行“(self.datascience)”固然不错,但下一步呢?回想我们的初衷:获取帖子标题、作者、点赞数和评论数。
为此,我们需要回到浏览器。如果再次在标识符上打开检查工具,您会看到我们的 span 标签嵌套在一个 div 标签内……而这个 div 自身又嵌套在另一个 div 中,依此类推。不断向上直到找到整条帖子的父级 div。
< div class = " thing id-t3_8qccuv even link self" ... >
...
< div class = " entry unvoted" >
< div class = " top-matter" >
< p class = " title" >
...
< span class = " domain" >
...
</ p>
</ div>
</ div>
</ div>
技术支持
如您所见,它们的共同父级在 DOM 结构中高出 4 层。省略号(…)代表不必要的“噪音”。但我们现在需要的是:为 domains 列表中的每个条目找到其对应帖子的父级 div 的引用。我们可以使用 BeautifulSoup 的方法来查找任何元素的父级。
for domain in soup. find_all( "span" , class_= "domain" ) :
if domain != "(self.datascience)" :
continue
parent_div = domain. parent. parent. parent. parent
print ( parent_div. text)
技术支持
运行脚本会打印所有站内帖子的全部文本。但您可能觉得这段代码看起来“不太好”。您是对的。仅依赖 DOM 的结构完整性从来都不安全。这是一种“刀耕火种”的解决方案,需要不断维护更新,是代码版的“定时炸弹”。
相反,我们来看看每个帖子的父级 div,看看能否直接在父级层面区分站内与站外链接。每个父级 div 都有一个名为“data-domain”的属性,而其值正是我们想要的!所有站内帖子都将 data-domain 设为“self.datascience”。
正如之前所说,我们可以用 BeautifulSoup 通过属性组合来搜索标签。幸运的是,Reddit 给每个帖子的父级 div 都加上了“thing”这个 class。
attrs = { 'class' : 'thing' , 'data-domain' : 'self.datascience' }
for post in soup. find_all( 'div' , attrs= attrs) :
print ( post. attrs[ 'data-domain' ] )
技术支持
“attrs”变量是一个字典,包含我们要搜索的属性及其对应的值。这种查找帖子的方法更安全,因为牵涉的可变因素更少。既然我们在搜索时已经将“self.datascience”作为参数,就不必再用 if 语句去跳过某些迭代了,因为我们保证只会拿到 data-domain 为“self.datascience”的帖子。
既然我们已经拿到了所需的帖子集合,接下来要做的就是从其子元素中提取信息。顺便说一句,这事确实就像您想的那样简单。
对于每个帖子,我们需要 4 条信息:
查看帖子 div 的结构,我们可以找到所有这些信息。
标题
这是最简单的。在每个帖子 div 中,有一个段落标签,嵌套在几层 div 之下。它很好找,因为带有“title”这个 class。
title = post. find( 'p' , class_= "title" ) . text
技术支持
post 对象来自我们之前的 for 循环。它同样是一个 BeautifulSoup 对象,但只包含帖子 div 内部的 DOM 结构。“find”方法只返回一个对象,而“find_all”会返回满足条件的对象列表。找到标签后,我们只需要其中的字符串标题,因此使用其 text 属性来读取。我们也可以通过“object.attrs['attribute']”来读取其他属性。
作者
这也相对简单,在帖子标题下方对任意作者名打开检查工具。您会看到作者名位于一个带有“author” class 的锚点标签中。
author = post. find( 'a' , class_= 'author' ) . text
技术支持
这个需要一点额外处理,但仍然简单。我们可以用与查找标题和作者相同的方法找到评论。
comments = post. find( 'a' , class_= 'comments' ) . text
技术支持
当您运行这段代码时,会得到类似“49 comments”的结果。这也可以,但如果只得到数字会更好。为此,我们需要再用一些 Python。
如果我们使用 Python 的“str.split()”函数,它会返回一个按空格分隔的字符串数组。在我们的例子中,将得到列表“["49", "comments"]”。很好!现在我们只需取第一个元素并保存。我们只需把函数链式追加到这一行即可。
comments = post. find( 'a' , class_= 'comments' ) . text. split( ) [ 0 ]
技术支持
但我们还没完,因为有时我们拿到的并不是数字,而是“comment”。当帖子没有任何评论时就会出现这种情况。既然我们知道这一点,只需检查结果是否为“comments”,并将其替换为“0”。
if comments == "comment" :
comments = 0
技术支持
点赞
获取点赞数很容易,逻辑与上面类似。
likes = post. find( "div" , attrs= { "class" : "score likes" } ) . text
技术支持
不过这里您会注意到我们使用了两个 class 的组合。这只是因为还有多个 div 具有“score”或“likes”这类 class,但只有“score”和“likes”的组合是唯一的。
如果点赞数为 0,我们会得到 0。但如果帖子非常新、尚无任何点赞,就会出现“•”。为了避免在最终结果中引起混淆,我们将其替换为“None”。
if likes == "•" :
likes = "None"
技术支持
将结果写入 CSV
到目前为止,我们已经有一个循环,可以为网页上的每个帖子提取标题、作者、点赞和评论。Python 提供了一个名为“csv”的优秀内置模块来读写 CSV 文件,秉承了 Python 的简单之道。无论如何,我们只需在循环块末尾添加一行,将帖子的详情追加到 CSV 文件中。
counter = 1
for post in posts:
. . .
post_line = [ counter, title, author, likes, comments]
with open ( 'output.csv' , 'a' ) as f:
writer = csv. writer( f)
writer. writerow( post_line)
counter += 1
技术支持
很直观吧?post_line 用于创建一个数组,存储需要用逗号分隔的元素。counter 变量用于记录我们已保存了多少条帖子。
翻到下一页
既然我们在统计已保存的帖子数量,只需将整个逻辑再包裹在一个循环里,请求新页面,直到记录了指定数量的帖子为止。
counter = 1
while ( counter <= 100 ) :
for post in posts:
next_button = soup. find( "span" , class_= "next-button" )
next_page_link = next_button. find( "a" ) . attrs[ 'href' ]
time. sleep( 2 )
page = requests. get( next_page_link, headers= headers)
soup = BeautifulSoup( page. text, 'html.parser' )
技术支持
我们一下子做了不少事,是吧?逐行看一下。首先,我们把 counter 变量上移了一层作用域,以便 while 循环可以使用它。
接着,我们知道 for 循环做什么了,那么其他行呢?“next_button”变量用于查找并保存“下一页”按钮。之后,我们可以在其中找到锚点标签并获取其“href”属性,将其保存到“next_page_link”。
然后,我们使用这个链接请求下一页,将结果重新保存到“page”,并用 BeautifulSoup 再生成一个新的 soup。
上面的代码在 100 条后停止,但您可以根据需要设置在任意帖子数量后停止。
负责任地抓取
上面我们没有讨论的一行是“time.sleep(2)”。这是因为它值得单独成节。任何 Web 服务器的资源都是有限的,因此我们必须确保不要耗尽它们。在几秒钟内请求数百个页面不仅可能导致您被封禁,而且也并不友好。
请记住,网站允许您进行爬取已经相当“友好”了;如果他们愿意,可以在前 10 到 20 次请求就检测到机器人,或者基于 Python 发送的请求对象对您进行识别。既然他们允许您在无需更换 IP 的情况下进行爬取,您就应该礼貌地“放慢”您的机器人。
您可以通过查看网站的 robots.txt 文件来了解其抓取策略。该文件通常位于网站根目录(例如:http://www.reddit.com/robots.txt )。
下一步做什么?
随您所愿。正如我们刚刚证明的,您在网页上能看到的任何内容都可以被抓取并保存在本地。您可以将我们刚获取的信息用于多种目的。仅凭我们找到的这四项信息,就能得出许多结论。
进一步分析这些信息,我们可以找出哪些类型的 Reddit 帖子更容易获得点赞、它们包含哪些词、由谁发布。或者反过来,我们可以通过分析点赞与评论的比值,制作一个“争议指数”计算器。
想象一条评论很多但几乎没有点赞的帖子。这很可能意味着该帖子包含能引发人们强烈反应的内容,但并非他们“喜欢”的内容。
可能性很多,如何使用这些信息取决于您。
如果您喜欢本教程,欢迎查看我们的使用 BeautifulSoup 爬取亚马逊 指南,以及我们的Python 课程 合集。
您可以在此处找到我们将要编写脚本的完整可运行示例。
什么是网络爬取(Web Scraping)?
好,那么网络爬取到底是什么?顾名思义,它是一种从网页“抓取”或提取数据的方法。您在浏览器中能看到的任何内容(包括本教程)都可以被抓取到本地硬盘上。
网络爬取有许多用途。对于任何数据分析,第一步都是数据获取。互联网是人类历史与知识的巨大存储库,您可以提取所需的任意信息,并据此开展您的工作。
在本教程中,我们将使用 Python 和 BeautifulSoup 4 包从一个 subreddit 获取信息。我们关注的是datascience 子版块。我们希望获取该子版块的前 1000 篇帖子并将其导出到 CSV 文件中。我们想知道是谁发的帖,以及它的点赞数和评论数。
本教程将涵盖以下内容:
requests获取网页注意:我们将使用 Reddit 网站的旧版本,因为它更轻量,加载更快,对您的机器负担更小。
前置要求
本教程假设您了解以下内容:
您可以在 DataCamp 的Python 初学者课程中学习上述技能。话虽如此,这里涉及的概念非常基础,即使只掌握少量 Python 知识也能读懂。
既然准备工作就绪,我们可以进入构建网络爬虫的第一步了。事实上,这也是编写任何 Python 脚本的第一步:导入。
在我们的爬虫中,将使用以下包:
requestsbeautifulsoup4您可以当然使用 pip 安装这些包,如下所示:
下载完这些包后,请将它们导入到您的代码中。
我们将使用 Python 内置的
csv模块将结果写入 CSV 文件。您或许注意到上面代码片段中有一点“古怪”。也就是我们下载了一个名为beautifulsoup4的包,却从一个名为bs4的模块中导入。这在 Python 中是合法的,尽管通常不被推崇,但并不“违法”。关于平台政策的重要说明
在使用我接下来展示的爬取技巧之前,请务必查看 Reddit 或您计划抓取的任何其他网站的最新服务条款。网站政策会随着时间变化。2024 年 9 月的Reddit 用户协议禁止未经事先书面同意的抓取(见第 7 条)。在继续之前,请务必确保您遵守该网站的最新使用指南。
爬取 Reddit 的第一步
我们的环境已经就绪。接下来,我们需要目标网页的 URL。在本教程中,我们使用 Reddit 的“datascience”子版块。在开始写脚本之前,我们需要做一些“实地考察”。打开浏览器,进入相关子版块。
请注意,我们将使用该子版块的旧版页面进行爬取。新版页面将一些关键信息隐藏在网页内部。虽然同样可以从新版网站中提取这些信息,但出于简化考虑,我们将使用信息更直观的旧版页面。
打开链接后,您会面对信息量的“洪流”。在这些内容里我们需要什么?通过梳理所有链接,您会发现 Reddit 帖子分为两类:
站内链接指向 Reddit 自身的内容,站外链接则相反。之所以重要,是因为我们只想要站内链接。也就是包含用户撰写文本的帖子,而不是仅指向其他网站的链接。
那么,知道了目标,如何提取呢?如果您查看帖子的标题,会看到其后跟着方括号中的一些文本。我们感兴趣的帖子后面跟着“(self.datascience)”。从字面上,我们可以推断“self”指代 Reddit 根目录,“.datascience”指代该子版块。
很好,这样我们就能区分站内与站外帖子了。接下来需要在 DOM 结构中定位它。是否可以通过在 DOM 结构中搜索标签、class 或 id 来找到这些标识符?当然可以!
在浏览器中,右键任意一个“self.datascience”链接,点击“检查元素”。大多数现代浏览器都有这个强大的工具,允许开发者动态遍历网页源代码中有意义的部分。如果您使用 Safari,请确保在 Safari 偏好设置中启用了开发者工具。您会看到一个面板,它已经定位到负责显示“(self.datascience)”标识符的源码部分。
在该面板中,您会看到这个标识符实际上是由一个锚点标签加载的。
但它被一个 span 标签包裹着。
这个 span 标签包含了我们在页面上看到的文本,即“(self.datascience)”。如您所见,它带有“domain”这个 class。您可以检查其他链接是否遵循相同格式,结果是——确实如此。
使用 BeautifulSoup 获取页面
我们知道页面上要找什么,这很好,但如何用 Python 读取页面内容?其实它的工作方式与人类通过浏览器读取页面内容非常相似。
首先,我们需要使用“requests”库请求网页。
现在我们有了一个 Response 对象,其中包含网页的原始文本。就目前而言,我们还无法对其做什么。我们手里只是一个包含整个 HTML 源代码的巨大字符串。要理解它,我们需要使用 BeautifulSoup 4。
请求头允许我们模拟浏览器访问。由于对机器人的响应与对浏览器的响应不同,而我们的参考对象是浏览器,因此最好获取浏览器的响应。
BeautifulSoup 允许我们通过搜索任意组合的 class、id 或标签名来查找特定标签。这是通过创建一棵语法树来实现的,不过其细节与我们的目标无关(也超出本教程范围)。
让我们来创建这棵语法树。
soup 就是一个 BeautifulSoup 对象,由一段原始源代码字符串创建。请注意我们需要指定 HTML 解析器。这是因为 BeautifulSoup 也可以从 XML 创建 soup。
查找目标标签
我们知道要找哪些标签(class 为“domain”的 span 标签),也有了 soup。接下来就是遍历 soup 并找到这些标签的所有实例。用 BeautifulSoup 来做这件事简单得令人发笑。
我们刚刚做了什么?我们在 soup 对象上调用了“find_all”方法,它会在自身中查找与传入参数匹配的锚点标签作为第二个参数。我们这里只传入了一个限制条件(class 必须为“domain”),但也可以与其他条件结合,或仅使用 id。
我们使用“class_=”是因为“class”是 Python 的保留关键字,用于定义类等。
如果您想传入多个参数,只需将第二个参数写成包含所需参数的字典,如下所示:
我们已经把所有 span 标签放进了“domains”列表,但我们想要的是“(self.datascience)”这样的域名。
对,现在您应该能看到页面上帖子类型的列表,并排除了不是“(self.datascience)”的那些。但我们基本上已经得到了想要的一切。我们现在持有所有站内帖子的引用。
查找所需信息
打印几行“(self.datascience)”固然不错,但下一步呢?回想我们的初衷:获取帖子标题、作者、点赞数和评论数。
为此,我们需要回到浏览器。如果再次在标识符上打开检查工具,您会看到我们的 span 标签嵌套在一个 div 标签内……而这个 div 自身又嵌套在另一个 div 中,依此类推。不断向上直到找到整条帖子的父级 div。
如您所见,它们的共同父级在 DOM 结构中高出 4 层。省略号(…)代表不必要的“噪音”。但我们现在需要的是:为 domains 列表中的每个条目找到其对应帖子的父级 div 的引用。我们可以使用 BeautifulSoup 的方法来查找任何元素的父级。
运行脚本会打印所有站内帖子的全部文本。但您可能觉得这段代码看起来“不太好”。您是对的。仅依赖 DOM 的结构完整性从来都不安全。这是一种“刀耕火种”的解决方案,需要不断维护更新,是代码版的“定时炸弹”。
相反,我们来看看每个帖子的父级 div,看看能否直接在父级层面区分站内与站外链接。每个父级 div 都有一个名为“data-domain”的属性,而其值正是我们想要的!所有站内帖子都将 data-domain 设为“self.datascience”。
正如之前所说,我们可以用 BeautifulSoup 通过属性组合来搜索标签。幸运的是,Reddit 给每个帖子的父级 div 都加上了“thing”这个 class。
“attrs”变量是一个字典,包含我们要搜索的属性及其对应的值。这种查找帖子的方法更安全,因为牵涉的可变因素更少。既然我们在搜索时已经将“self.datascience”作为参数,就不必再用 if 语句去跳过某些迭代了,因为我们保证只会拿到 data-domain 为“self.datascience”的帖子。
既然我们已经拿到了所需的帖子集合,接下来要做的就是从其子元素中提取信息。顺便说一句,这事确实就像您想的那样简单。
从 Reddit 提取信息
对于每个帖子,我们需要 4 条信息:
查看帖子 div 的结构,我们可以找到所有这些信息。
标题
这是最简单的。在每个帖子 div 中,有一个段落标签,嵌套在几层 div 之下。它很好找,因为带有“title”这个 class。
post 对象来自我们之前的 for 循环。它同样是一个 BeautifulSoup 对象,但只包含帖子 div 内部的 DOM 结构。“find”方法只返回一个对象,而“find_all”会返回满足条件的对象列表。找到标签后,我们只需要其中的字符串标题,因此使用其 text 属性来读取。我们也可以通过“object.attrs['attribute']”来读取其他属性。
作者
这也相对简单,在帖子标题下方对任意作者名打开检查工具。您会看到作者名位于一个带有“author” class 的锚点标签中。
评论
这个需要一点额外处理,但仍然简单。我们可以用与查找标题和作者相同的方法找到评论。
当您运行这段代码时,会得到类似“49 comments”的结果。这也可以,但如果只得到数字会更好。为此,我们需要再用一些 Python。
如果我们使用 Python 的“str.split()”函数,它会返回一个按空格分隔的字符串数组。在我们的例子中,将得到列表“["49", "comments"]”。很好!现在我们只需取第一个元素并保存。我们只需把函数链式追加到这一行即可。
但我们还没完,因为有时我们拿到的并不是数字,而是“comment”。当帖子没有任何评论时就会出现这种情况。既然我们知道这一点,只需检查结果是否为“comments”,并将其替换为“0”。
点赞
获取点赞数很容易,逻辑与上面类似。
不过这里您会注意到我们使用了两个 class 的组合。这只是因为还有多个 div 具有“score”或“likes”这类 class,但只有“score”和“likes”的组合是唯一的。
如果点赞数为 0,我们会得到 0。但如果帖子非常新、尚无任何点赞,就会出现“•”。为了避免在最终结果中引起混淆,我们将其替换为“None”。
将结果写入 CSV
到目前为止,我们已经有一个循环,可以为网页上的每个帖子提取标题、作者、点赞和评论。Python 提供了一个名为“csv”的优秀内置模块来读写 CSV 文件,秉承了 Python 的简单之道。无论如何,我们只需在循环块末尾添加一行,将帖子的详情追加到 CSV 文件中。
很直观吧?post_line 用于创建一个数组,存储需要用逗号分隔的元素。counter 变量用于记录我们已保存了多少条帖子。
翻到下一页
既然我们在统计已保存的帖子数量,只需将整个逻辑再包裹在一个循环里,请求新页面,直到记录了指定数量的帖子为止。
我们一下子做了不少事,是吧?逐行看一下。首先,我们把 counter 变量上移了一层作用域,以便 while 循环可以使用它。
接着,我们知道 for 循环做什么了,那么其他行呢?“next_button”变量用于查找并保存“下一页”按钮。之后,我们可以在其中找到锚点标签并获取其“href”属性,将其保存到“next_page_link”。
然后,我们使用这个链接请求下一页,将结果重新保存到“page”,并用 BeautifulSoup 再生成一个新的 soup。
上面的代码在 100 条后停止,但您可以根据需要设置在任意帖子数量后停止。
负责任地抓取
上面我们没有讨论的一行是“time.sleep(2)”。这是因为它值得单独成节。任何 Web 服务器的资源都是有限的,因此我们必须确保不要耗尽它们。在几秒钟内请求数百个页面不仅可能导致您被封禁,而且也并不友好。
请记住,网站允许您进行爬取已经相当“友好”了;如果他们愿意,可以在前 10 到 20 次请求就检测到机器人,或者基于 Python 发送的请求对象对您进行识别。既然他们允许您在无需更换 IP 的情况下进行爬取,您就应该礼貌地“放慢”您的机器人。
您可以通过查看网站的 robots.txt 文件来了解其抓取策略。该文件通常位于网站根目录(例如:http://www.reddit.com/robots.txt)。
下一步做什么?
随您所愿。正如我们刚刚证明的,您在网页上能看到的任何内容都可以被抓取并保存在本地。您可以将我们刚获取的信息用于多种目的。仅凭我们找到的这四项信息,就能得出许多结论。
进一步分析这些信息,我们可以找出哪些类型的 Reddit 帖子更容易获得点赞、它们包含哪些词、由谁发布。或者反过来,我们可以通过分析点赞与评论的比值,制作一个“争议指数”计算器。
想象一条评论很多但几乎没有点赞的帖子。这很可能意味着该帖子包含能引发人们强烈反应的内容,但并非他们“喜欢”的内容。
可能性很多,如何使用这些信息取决于您。
如果您喜欢本教程,欢迎查看我们的使用 BeautifulSoup 爬取亚马逊指南,以及我们的Python 课程合集。