个性化阅读
专注于IT技术分析

使用Python和BeautifulSoup 4抓取Reddit

本文概述

使用Python和BeautifulSoup 4抓取Reddit1

你可以找到我们将在此处编写的脚本的完整示例。

什么是网络爬虫?

对, 那么网页抓取到底是什么?顾名思义, 它是一种”抓取”或从网页提取数据的方法。你可以使用浏览器在互联网上看到的任何内容(包括本教程)都可以刮到本地硬盘上。

网页抓取有很多用途。对于任何数据分析, 第一步都是数据采集。互联网是人类所有历史和知识的巨大储存库, 你可以提取所需的任何内容, 并根据自己的意愿进行处理。

在我们的教程中, 我们将使用Python和BeautifulSoup 4包从subreddit获取信息。我们对datascience subreddit感兴趣。我们想要在subreddit上获取前1000个帖子, 并将其导出到CSV文件。我们想知道谁发布了它, 以及它有多少喜欢和评论。

我们将在本教程中介绍:

  • 使用请求获取网页
  • 在浏览器中分析网页以获取信息
  • 使用BeautifulSoup从原始HTML中提取信息

注意:我们将使用旧版本的Reddit网站, 因为它加载起来更轻巧, 因此在你的计算机上的工作量也更少。

先决条件

本教程假定你了解以下内容:

  • 在计算机上运行Python脚本
    • HTML结构的基础知识

你可以在srcmini的Python入门课程中学习上述技能。话虽这么说, 这里使用的概念非常少, 并且你可以了解很少的Python知识。

既然完成了, 我们就可以进入制作网络刮板的第一部分了。实际上, 编写任何Python脚本的第一部分是:import。

在我们的刮板中, 我们将使用以下软件包:

  • 要求
  • 美丽的汤4

你当然可以使用pip安装这些软件包, 如下所示:

pip install package_name

下载完软件包后, 继续并将其导入代码中。

import requests
import csv
import time
from bs4 import BeautifulSoup4

我们将使用Python的内置csv模块将结果写入CSV文件。你可能已经注意到上面的片段中有一些古怪的地方。也就是说, 我们下载了一个名为beautifulsoup4的软件包, 但是我们是从一个名为bs4的模块中导入的。这在Python中是合法的, 尽管人们通常对此表示反对, 但这并不完全违法。

第一步

因此, 我们已经设置好环境并准备就绪。接下来, 我们需要要抓取的网页的URL。对于我们的教程, 我们使用Reddit的’datascience’subreddit。在开始编写脚本之前, 我们需要做一些现场工作。打开网络浏览器, 然后转到有问题的subreddit。

请注意, 我们将使用较旧版本的subreddit作为我们的抓取工具。较新的版本在网页的底部隐藏了一些关键信息。也可以从新站点中提取此信息, 但为简单起见, 我们将使用较旧的版本, 其中列出了所有内容。

打开链接后, 你会遇到一堆信息超载的情况。我们在所有这一切中到底需要什么?好了, 研究完所有链​​接后, 你会发现Reddit帖子有两种类型:

  • 入站
  • 出站

入站链接是指向Reddit本身上的内容的链接, 而出站则恰好相反。这很重要, 因为我们只需要入站链接。帖子包含用户撰写的文字, 而不仅仅是指向其他网站的链接。

所以, 我们知道我们想要什么, 我们如何去提取它?如果你查看帖子的标题, 你会发现它的后面是括号内的一些文本。我们感兴趣的帖子后跟”(self.datascience)”。从逻辑上讲, 我们可以假设” self”是指Reddit根目录, “。datascience”是指subreddit。

太好了, 因此我们有一种方法可以识别哪些帖子是入站的, 哪些帖子是出站的。现在, 我们需要在DOM结构中进行标识。有没有一种方法可以通过搜索DOM结构中的标签, 类或ID在DOM中找到这些标识符?当然!

在网络浏览器中, 右键单击任何” self.datascience”链接, 然后单击”检查元素”。大多数现代的Web浏览器都具有此强大的工具, 可让Web开发人员动态地遍历网页源代码中有意义的部分。如果你使用的是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)”。如你所见, 它标有”域”类。你可以检查所有其他链接, 以查看它们是否遵循相同的格式, 并且确定是否足够;他们是这样。

获取页面

我们知道页面上想要的东西, 这一切都很好, 但是我们如何使用Python读取页面的内容?嗯, 它的工作方式几乎与人类从网络浏览器读取页面内容的方式相同。

首先, 我们需要使用”请求”库请求网页。

url = "https://old.reddit.com/r/datascience/"
# Headers to mimic a browser visit
headers = {'User-Agent': 'Mozilla/5.0'}

# Returns a requests.models.Response object
page = requests.get(url, headers=headers)

现在, 我们有了一个Response对象, 其中包含网页的原始文本。到目前为止, 我们对此无能为力。我们所拥有的只是一个巨大的字符串, 其中包含HTML文件的整个源代码。为此, 我们需要使用BeautifulSoup 4。

标头将使我们能够模仿浏览器的访问。由于对漫游器的响应与对浏览器的响应不同, 并且我们的参考点是浏览器, 因此最好获得浏览器的响应。

BeautifulSoup将允许我们通过搜索类, id或标签名称的任意组合来查找特定的标签。这是通过创建语法树来完成的, 但是语法树与我们的目标无关(不在本教程的讨论范围之内)。

因此, 让我们继续创建该语法树。

soup = BeautifulSoup(page.text, 'html.parser')

汤只是通过获取一串原始源代码创建的BeautifulSoup对象。请记住, 我们需要指定html解析器。这是因为BeautifulSoup也可以使用XML创建汤。

寻找我们的标签

我们知道我们想要什么标签(带有” domain”类的span标签), 并且有汤。接下来是遍历汤并找到这些标签的所有实例。你可能会因为使用BeautifulSoup这么简单而笑。

domains = soup.find_all("span", class_="domain")

我们刚刚做了什么?我们在汤对象上调用了” find_all”方法, 该方法将查找所有锚点标签, 并将这些参数作为第二个参数传入。我们只传递了一个约束(类必须是”域”), 但是我们可以将其与许多其他东西结合在一起, 甚至可以使用id。

我们使用” class_ =”, 因为” class”是Python保留的用于定义类等的关键字

如果要传递多个参数, 则要做的就是使第二个参数成为要包含的参数的字典, 如下所示:

soup.find_all("span", {"class": "domain", "height", "100px"})

我们的”域”列表中包含所有span标签, 但是我们想要的是”(self.datascience)”域。

for domain in domains:
    if domain != "(self.datascience)":
        continue

    print(domain.text)

正确, 现在你应该会在页面上看到列表中的信息类型列表, 但不包括不是”(self.datascience)”信息的信息类型。但是我们基本上拥有了我们想要的一切。现在, 我们可以引用所有入站的帖子。

查找我们的信息

很棒, 我们正在印制两行”(self.datascience)”, 但是接下来呢?好吧, 请回想一下我们最初的目标。获取帖子标题, 作者, 喜欢和评论数。

为此, 我们必须返回浏览器。如果再次使检查器显示在我们的标识符上, 你会看到我们的span标签嵌套在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个级别。省略号(…)表示不必要的颤动。但是, 我们现在需要的是对域列表中每个帖子的post 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都有一个名为”数据域”的属性, 其值正是我们想要的!所有入站帖子的数据域均设置为” self.datascience”。

如前所述, 我们可以使用BeautifulSoup搜索具有属性组合的标签。对我们来说幸运的是, Reddit选择将每个帖子的父div类别都用”事物”分类。

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语句来跳过任何迭代, 因为我们保证只接收带有’self.datascience’数据域的帖子属性。

现在我们有了所需的所有信息, 剩下的就是从孩子那里提取信息。顺便说一下, 这就像你认为的那样简单。

提取我们的信息

对于每个帖子, 我们需要4条信息。

  • 标题
  • 作者
  • 喜欢
  • 注释

如果我们看一下post div的结构, 我们可以找到所有这些信息。

标题

这是迄今为止最简单的一种。在每个post div中, 都有一个嵌套在div几层下的段落标签。它很容易找到, 因为它附加了” title”类。

title = post.find('p', class_="title").text

post对象位于我们之前的for循环中。它也是BeautifulSoup对象, 但它仅包含post div中的DOM结构。 find方法仅返回一个对象, 而find_all则返回满足条件的对象列表。找到标签后, 我们只想要包含标题的字符串, 因此我们使用它的text属性读取它。我们还可以使用” object.attrs [attribute]]来读取其他属性。

作者

这也相对简单, 请在帖子标题下使用任何作者的名字打开检查器。你会看到作者名称在分类为”作者”的锚标记中。

author = post.find('a', class_='author').text

评论

这需要一些额外的工作, 但仍然很简单。我们可以按照找到标题和作者的方式找到评论。

comments = post.find('a', class_='comments').text

运行此命令时, 你会看到类似” 49条评论”的信息。可以, 但是如果我们只知道这个号码, 那就更好了。为此, 我们需要使用更多的Python。

如果我们使用Python的” str.split()”函数, 它将返回字符串中所有元素的数组, 并用空格分隔。在我们的例子中, 我们将得到列表[[” 49″, ” comments”]’。太好了!现在, 我们要做的就是获取第一个元素并存储它, 我们要做的就是将函数附加到我们的线。

comments = post.find('a', class_='comments').text.split()[0]

但是我们仍然没有完成, 因为有时我们没有电话号码。我们得到”评论”。当帖子没有评论时, 就会发生这种情况。既然我们知道这一点, 我们要做的就是检查结果是否为” comments”, 并将其替换为” 0″。

if comments == "comment":
    comments = 0

喜欢

找到喜欢的次数是小菜一碟, 与我们上面使用的逻辑相符。

likes = post.find("div", attrs={"class": "score likes"}).text

不过在这里, 你可能会注意到我们使用了两个类的组合。这仅仅是因为还有其他多个div类别为”得分”或”喜欢”的div, 但只有一个具有”得分”和”喜欢”的组合的div。

如果点赞的次数为0, 则我们得到0。但是, 如果帖子太新以至于没有点赞, 就会发生一些奇怪的事情。我们得到”•”。让我们将其替换为”无”, 这样就不会在最终结果中造成混淆。

if likes == "•":
    likes = "None"

将结果写入CSV

到目前为止, 我们已经有了循环, 可以提取网页上每个帖子的标题, 作者, 喜欢和评论。 Python有一个很棒的内置模块, 可以按照pythonic的方式写入和读取名为” csv”的CSV文件:保持简单。无论如何, 我们要做的就是在循环块的末尾添加一行, 以将帖子的详细信息附加到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:
        # Getting the title, author, ...
        # Writing to CSV and incrementing counter...

    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')

我们只是做了很多事情, 不是吗?让我们一次看看它。首先, 我们将计数器变量上移了一个块, 以便while循环可以使用它。

接下来, 我们知道for循环的作用, 但其他循环又如何。 ” next_button”变量用于查找和存储” next”按钮。之后, 我们可以在其中找到锚标记并获取’href’属性;我们将其存储在” next_page_link”中。

我们可以使用此链接请求下一页并将其存储回”页面”中, 并使用BeautifulSoup制作另一种汤。

上面的代码段在100条后停止, 但你可以在任意数量的帖子后停止它。

负责任地刮

我们上面没有谈论的那一行是” time.sleep(2)”。那是因为该行应有其自己的部分。任何网络服务器的资源都是有限的, 因此我们有责任确保不会耗尽所有资源。它不仅使你被禁止在几秒钟内请求数百个页面, 而且也不好。

重要的是要记住, 即使允许你抓取它们, 网站也非常不错, 因为如果他们愿意, 他们可以在前10到20个请求中检测到漫游器, 甚至可以根据Python发送的请求对象来捕获你。它们非常好用, 它们使你无需旋转IP即可抓取, 因此你应该为它们提供降低机器人速度的服务。

你可以通过查看网站的robots.txt文件来查找网站的抓取策略。通常可以在网站的根目录(例如http://www.reddit.com/robots.txt)找到该文件。

接下来是什么?

好吧, 无论你想要什么。正如我们刚刚证明的那样, 你在网络上看到的所有内容都可以在本地抓取和存储。你可以将我们刚刚获得的信息用于多种目的。仅凭我们发现的四点信息, 我们可以得出很多结论。

通过进一步分析我们的信息, 我们可以确定哪种帖子最受喜欢, 它们包含的单词以及发布者。或者相反, 我们可以通过分析喜欢与评论的比率来找到一个有争议的计算器。

考虑一个收到很多评论但没有喜欢的帖子。这最有可能意味着该帖子包含了引起人们共鸣的内容, 但不一定是他们喜欢的内容。

可能性很多, 由你自己决定如何使用这些信息。

相关课程

如果你想了解有关Python的更多信息, 请查阅srcmini的以下课程:

数据科学Python简介

用Python导入数据(第1部分)

赞(0)
未经允许不得转载:srcmini » 使用Python和BeautifulSoup 4抓取Reddit

评论 抢沙发

评论前必须登录!