使用Python和BeautifulSoup 4抓取Reddit

本文概述

  • 什么是网络爬虫?
  • 先决条件
  • 第一步
  • 获取页面
  • 寻找我们的标签
  • 查找我们的信息
  • 提取我们的信息
  • 将结果写入CSV
  • 移至下一页
  • 负责任地刮
  • 接下来是什么?
  • 相关课程
使用Python和BeautifulSoup 4抓取Reddit

文章图片
你可以找到我们将在此处编写的脚本的完整示例。
什么是网络爬虫? 对, 那么网页抓取到底是什么?顾名思义, 它是一种” 抓取” 或从网页提取数据的方法。你可以使用浏览器在互联网上看到的任何内容(包括本教程)都可以刮到本地硬盘上。
网页抓取有很多用途。对于任何数据分析, 第一步都是数据采集。互联网是人类所有历史和知识的巨大储存库, 你可以提取所需的任何内容, 并根据自己的意愿进行处理。
在我们的教程中, 我们将使用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="http://www.srcmini.com/r/datascience"> self.datascience< /a>

但这是用span标签括起来的。
< span class="domain"> "(" < a href="http://www.srcmini.com/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。
【使用Python和BeautifulSoup 4抓取Reddit】标头将使我们能够模仿浏览器的访问。由于对漫游器的响应与对浏览器的响应不同, 并且我们的参考点是浏览器, 因此最好获得浏览器的响应。
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)": continueprint(domain.text)

正确, 现在你应该会在页面上看到列表中的信息类型列表, 但不包括不是” (self.datascience)” 信息的信息类型。但是我们基本上拥有了我们想要的一切。现在, 我们可以引用所有入站的帖子。
查找我们的信息 很棒, 我们正在印制两行” (self.datascience)” , 但是接下来呢?好吧, 请回想一下我们最初的目标。获取帖子标题, 作者, 喜欢和评论数。
为此, 我们必须返回浏览器。如果再次使检查器显示在我们的标识符上, 你会看到我们的span标签嵌套在div标签中……它本身嵌套在另一个div标签中, 依此类推。继续前进, 你将获得上一篇文章的父级div。
< div class=" thing id-t3_8qccuv evenlink 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)": continueparent_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 = 1while (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部分)

    推荐阅读