如何使用Python3抓取网页并将内容发布到Twitter

来自菜鸟教程
跳转至:导航、​搜索

作者选择 计算机历史博物馆 作为 Write for DOnations 计划的一部分接受捐赠。

介绍

Twitter 机器人是一种管理社交媒体以及从微博网络中提取信息的强大方式。 通过利用 Twitter 的多功能 API,机器人可以做很多事情:tweet、转发、“favorite-tweet”、关注具有特定兴趣的人、自动回复等等。 尽管人们可以并且确实滥用他们的机器人的力量,导致其他用户的负面体验,但研究表明,人们将 Twitter 机器人视为可靠的信息来源。 例如,即使您不在线,机器人也可以让您的关注者参与内容。 一些机器人甚至提供重要和有用的信息,例如 @EarthquakesSF。 机器人的应用是无限的。 截至 2019 年,据估计,在 Twitter 上的所有推文 中,机器人约占 24% o。

在本教程中,您将使用 这个适用于 Python 的 Twitter API 库 构建一个 Twitter 机器人。 您将使用 Twitter 帐户中的 API 密钥来授权您的机器人并构建一个能够从两个网站抓取内容的工具。 此外,您将对您的机器人进行编程,以在设定的时间间隔内交替发送来自这两个网站的内容。 请注意,您将在本教程中使用 Python 3。

先决条件

您将需要以下内容来完成本教程:

注意: 您将在 Twitter 上设置一个开发者帐户,这涉及到 Twitter 的应用程序审查,然后您才能访问此机器人所需的 API 密钥。 第 1 步介绍了完成申请的具体细节。


第 1 步 — 设置您的开发者帐户并访问您的 Twitter API 密钥

在开始编写机器人代码之前,您需要 Twitter 的 API 密钥来识别机器人的请求。 在此步骤中,您将设置您的 Twitter 开发人员帐户并访问您的 Twitter 机器人的 API 密钥。

要获取您的 API 密钥,请前往 developer.twitter.com 并通过单击页面右上角的 Apply 在 Twitter 上注册您的机器人应用程序。

现在点击【X13X】申请开发者账号【X46X】。

接下来,单击 Continue 将您的 Twitter 用户名与您将在本教程中构建的机器人应用程序相关联。

在下一页上,出于本教程的目的,您将选择 我请求访问以供我个人使用 选项,因为您将构建一个用于您自己的个人教育用途的机器人。

选择您的 Account NameCountry 后,继续下一部分。 对于 您对哪些用例感兴趣?,请选择 发布和策划推文学生项目/学习编码 选项。 这些类别最能代表您完成本教程的原因。

然后提供您正在尝试构建的机器人的描述。 Twitter 要求这样做以防止机器人滥用; 2018 年,他们引入了此类审查。 在本教程中,您将从 The New StackThe Coursera Blog 中抓取以技术为中心的内容。

在决定在 描述 框中输入什么内容时,为了本教程的目的,请在以下几行中为您的答案建模:

我正在按照教程构建一个 Twitter 机器人,它将从 thenewstack.io(新堆栈)和 blog.coursera.org(Coursera 的博客)等网站抓取内容并发送推文他们的引述。 抓取的内容将被聚合,并将通过 Python 生成器函数以循环方式发布。

最后,为 选择 你的产品、服务或分析是否会使 Twitter 内容或派生信息提供给政府实体?

接下来,接受 Twitter 的条款和条件,点击提交申请,然后验证您的电子邮件地址。 在您提交此表单后,Twitter 将向您发送一封验证电子邮件。

验证电子邮件后,您将获得一个 Application under review 页面,其中包含申请流程的反馈表。

您还将收到另一封来自 Twitter 的关于审查的电子邮件:

Twitter 申请审核流程的时间表可能会有很大差异,但 Twitter 通常会在几分钟内确认这一点。 但是,如果您的申请审核时间超过此时间,这并不罕见,您应该会在一两天内收到。 收到确认后,Twitter 已授权您生成密钥。 在 developer.twitter.com/apps 上单击应用程序的详细信息按钮后,您可以在 Keys and tokens 选项卡下访问这些内容。

最后转到应用页面上的 Permissions 选项卡,并将 Access Permission 选项设置为 Read and Write,因为您也想编写推文内容。 通常,您会将只读模式用于分析趋势、数据挖掘等研究目的。 最后一个选项允许用户将聊天机器人集成到他们现有的应用程序中,因为聊天机器人需要访问直接消息。

您可以访问 Twitter 的强大 API,这将是您的机器人应用程序的关键部分。 现在您将设置您的环境并开始构建您的机器人。

第 2 步 — 构建基本要素

在此步骤中,您将编写代码以使用 API 密钥通过 Twitter 对您的机器人进行身份验证,并通过您的 Twitter 句柄发出第一条编程推文。 这将成为您实现构建 Twitter 机器人目标的一个很好的里程碑,该机器人从 The New StackCoursera 博客 中抓取内容并定期发布推文。

首先,您将为您的项目设置一个项目文件夹和一个特定的编程环境。

创建您的项目文件夹:

mkdir bird

移动到您的项目文件夹:

cd bird

然后为您的项目创建一个新的 Python 虚拟环境:

python3 -m venv bird-env

然后使用以下命令激活您的环境:

source bird-env/bin/activate

这将在终端窗口的提示中附加一个 (bird-env) 前缀。

现在转到您的文本编辑器并创建一个名为 credentials.py 的文件,该文件将存储您的 Twitter API 密钥:

nano credentials.py

添加以下内容,将突出显示的代码替换为来自 Twitter 的密钥:

鸟/凭证.py

ACCESS_TOKEN='your-access-token'
ACCESS_SECRET='your-access-secret'
CONSUMER_KEY='your-consumer-key'
CONSUMER_SECRET='your-consumer-secret'

现在,您将安装用于向 Twitter 发送请求的主要 API 库。 对于此项目,您需要以下库:nltkrequeststwitterlxmlrandom 和 [X117X ]。 randomtime 是 Python 标准库的一部分,因此您不需要单独安装这些库。 要安装剩余的库,您将使用 pip,它是 Python 的包管理器。

打开您的终端,确保您位于项目文件夹中,然后运行以下命令:

pip3 install lxml nltk requests twitter
  • lxmlrequests:您将使用它们进行网页抓取。
  • twitter:这是用于对 Twitter 服务器进行 API 调用的库。
  • nltk:(自然语言工具包)您将使用将博客段落拆分为句子。
  • random:您将使用它来随机选择整个抓取的博客文章的一部分。
  • time:您将用于在某些操作后让您的机器人定期休眠。

一旦你安装了这些库,你就可以开始编程了。 现在,您将把您的凭据导入到将运行机器人的主脚本中。 在 credentials.py 旁边,从您的文本编辑器中在 bird 项目目录中创建一个文件,并将其命名为 bot.py

nano bot.py

在实践中,随着机器人变得越来越复杂,您会将机器人的功能分散到多个文件中。 但是,在本教程中,出于演示目的,您将把所有代码放在一个脚本 bot.py 中。

首先,您将通过授权您的机器人来测试您的 API 密钥。 首先将以下代码段添加到 bot.py

鸟/bot.py

import random
import time

from lxml.html import fromstring
import nltk
nltk.download('punkt')
import requests
from twitter import OAuth, Twitter

import credentials

在这里,您导入所需的库; 在某些情况下,您可以从库中导入必要的 函数 。 您将在后面的代码中使用 fromstring 函数将抓取网页的字符串源转换为树形结构,以便更轻松地从页面中提取相关信息。 OAuth 将帮助您从您的密钥构建身份验证对象,而 Twitter 将构建主要 API 对象,以便与 Twitter 的服务器进行所有进一步的通信。

现在使用以下行扩展 bot.py

鸟/bot.py

...
tokenizer = nltk.data.load('tokenizers/punkt/english.pickle')

oauth = OAuth(
        credentials.ACCESS_TOKEN,
        credentials.ACCESS_SECRET,
        credentials.CONSUMER_KEY,
        credentials.CONSUMER_SECRET
    )
t = Twitter(auth=oauth)

nltk.download('punkt') 下载解析段落并将它们标记(拆分)成更小的组件所必需的数据集。 tokenizer 是您稍后将在代码中用于拆分用英语编写的段落的对象。

oauth 是通过向导入的 OAuth 类提供 API 密钥而构建的身份验证对象。 您通过 t = Twitter(auth=oauth) 行验证您的机器人。 ACCESS_TOKENACCESS_SECRET 有助于识别您的应用程序。 最后,CONSUMER_KEYCONSUMER_SECRET 有助于识别应用程序与 Twitter 交互的句柄。 您将使用此 t 对象将您的请求传达给 Twitter。

现在保存此文件并使用以下命令在终端中运行它:

python3 bot.py

您的输出将类似于以下内容,这意味着您的授权已成功:

Output[nltk_data] Downloading package punkt to /Users/binaryboy/nltk_data...
[nltk_data]   Package punkt is already up-to-date!

如果您确实收到错误消息,请使用您的 Twitter 开发者帐户 中的 API 密钥验证您保存的 API 密钥,然后重试。 还要确保正确安装了所需的库。 如果没有,请再次使用 pip3 安装它们。

现在您可以尝试以编程方式发布推文。 在终端上键入带有 -i 标志的相同命令,以在执行脚本后打开 Python 解释器:

python3 -i bot.py

接下来,键入以下内容以通过您的帐户发送推文:

t.statuses.update(status="Just setting up my Twttr bot")

现在在浏览器中打开您的 Twitter 时间线,您将在时间线顶部看到一条推文,其中包含您发布的内容。

通过键入 quit()CTRL + D 关闭解释器。

您的机器人现在具有发推文的基本功能。 要开发您的机器人来发布有用的内容,您将在下一步中结合网络抓取。

第 3 步 — 为您的推文内容抓取网站

为了在您的时间线中引入一些更有趣的内容,您将从 、New StackCoursera 博客 中抓取内容,然后以推文的形式将这些内容发布到 Twitter。 通常,要从目标网站中抓取适当的数据,您必须尝试其 HTML 结构。 来自您将在本教程中构建的机器人的每条推文都将包含指向所选网站的博客文章的链接,以及来自该博客的随机引用。 您将在特定于从 Coursera 抓取内容的函数中实现此过程,因此您将其命名为 scrape_coursera()

先打开bot.py

nano bot.py

scrape_coursera() 函数添加到文件末尾:

鸟/bot.py

...
t = Twitter(auth=oauth)


def scrape_coursera():

要从博客中抓取信息,您首先需要从 Coursera 的服务器请求相关网页。 为此,您将使用 requests 库中的 get() 函数。 get() 接收一个 URL 并获取相应的网页。 因此,您将 blog.coursera.org 作为参数传递给 get()。 但是您还需要在 GET 请求中提供标头,这将确保 Coursera 的服务器将您识别为真正的客户。 将以下突出显示的行添加到 scrape_coursera() 函数以提供标题:

鸟/bot.py

def scrape_coursera():
    HEADERS = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5)'
                      ' AppleWebKit/537.36 (KHTML, like Gecko) Cafari/537.36'
        }

此标头将包含与在特定操作系统上运行的已定义 Web 浏览器有关的信息。 只要此信息(通常称为 User-Agent)对应于真实的 Web 浏览器和操作系统,标题信息是否与您计算机上的实际 Web 浏览器和操作系统一致并不重要。 因此,此标头适用于所有系统。

定义标题后,添加以下突出显示的行以通过指定博客网页的 URL 向 Coursera 发出 GET 请求:

鸟/bot.py

...
def scrape_coursera():
    HEADERS = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5)'
                      ' AppleWebKit/537.36 (KHTML, like Gecko) Cafari/537.36'
        }
    r = requests.get('https://blog.coursera.org', headers=HEADERS)
    tree = fromstring(r.content)

这会将网页获取到您的机器并将整个网页的信息保存在变量 r 中。 您可以使用 rcontent 属性来评估网页的 HTML 源代码。 因此,r.content 的值与您通过右键单击页面并选择 Inspect Element 选项在浏览器中检查网页时看到的值相同。

在这里,您还添加了 fromstring 函数。 您可以将网页的源代码传递给从lxml库中导入的fromstring函数来构造网页的tree结构。 这种 tree 结构将允许您方便地访问网页的不同部分。 HTML 源代码具有特定的树状结构; 每个元素都包含在 <html> 标记中并在其后嵌套。

现在,在浏览器中打开 https://blog.coursera.org 并使用浏览器的开发人员工具检查其 HTML 源代码。 右键单击页面并选择 Inspect Element 选项。 您会看到浏览器底部出现一个窗口,显示页面的部分 HTML 源代码。

接下来,右键单击任何可见博客文章的缩略图,然后检查它。 HTML 源代码将突出显示定义该博客缩略图的相关 HTML 行。 您会注意到此页面上的所有博客文章都定义在 <div> 标记中,其中 class"recent"

因此,在您的代码中,您将通过它们的 XPath 使用所有此类博客文章 div 元素,这是一种寻址网页元素的便捷方式。

为此,请在 bot.py 中扩展您的函数,如下所示:

鸟/bot.py

...
def scrape_coursera():
    HEADERS = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5)'
                      ' AppleWebKit/537.36 (KHTML, like Gecko) Cafari/537.36'
                    }
    r = requests.get('https://blog.coursera.org', headers=HEADERS)
    tree = fromstring(r.content)
    links = tree.xpath('//div[@class="recent"]//div[@class="title"]/a/@href')
    print(links)

scrape_coursera()

在这里,XPath(传递给 tree.xpath() 的字符串)表示您想要来自整个网页源的 div 元素,属于 class [X155X ]。 //对应搜索整个网页,div告诉函数只提取div元素,[@class="recent"]要求它只提取那些[X171X ] 元素的 class 属性值为 "recent"

但是,您不需要这些元素本身,您只需要它们指向的链接,这样您就可以访问各个博客文章以抓取它们的内容。 因此,您使用博客文章的先前 div 标记内的 href 锚标记的值提取所有链接。

到目前为止,为了测试您的程序,您在 bot.py 的末尾调用 scrape_coursera() 函数。

保存并退出bot.py

现在使用以下命令运行 bot.py

python3 bot.py

在您的输出中,您将看到如下所示的 URL list

Output['https://blog.coursera.org/career-stories-from-inside-coursera/', 'https://blog.coursera.org/unlock-the-power-of-data-with-python-university-of-michigan-offers-new-programming-specializations-on-coursera/', ...]

验证输出后,您可以从 bot.py 脚本中删除最后两行突出显示的行:

鸟/bot.py

...
def scrape_coursera():
    ...
    tree = fromstring(r.content)
    links = tree.xpath('//div[@class="recent"]//div[@class="title"]/a/@href')
    ~~print(links)~~

~~scrape_coursera()~~

现在使用以下突出显示的行扩展 bot.py 中的函数以从博客文章中提取内容:

鸟/bot.py

...
def scrape_coursera():
    ...
    links = tree.xpath('//div[@class="recent"]//div[@class="title"]/a/@href')
    for link in links:
        r = requests.get(link, headers=HEADERS)
        blog_tree = fromstring(r.content)

您遍历每个链接,获取相应的博客文章,从文章中提取一个随机句子,然后将该句子作为引用发送推文,以及相应的 URL。 提取随机句子涉及三个部分:

  1. 抓取博客文章中的所有段落作为列表。
  2. 从段落列表中随机选择一个段落。
  3. 从这一段中随机选择一个句子。

您将为每篇博文执行这些步骤。 为了获取一个,您对其链接发出 GET 请求。

现在您可以访问博客的内容,您将介绍执行这三个步骤以从中提取所需内容的代码。 将以下扩展添加到执行三个步骤的抓取功能中:

鸟/bot.py

...
def scrape_coursera():
    ...
    for link in links:
        r = requests.get(link, headers=HEADERS)
        blog_tree = fromstring(r.content)
        paras = blog_tree.xpath('//div[@class="entry-content"]/p')
        paras_text = [para.text_content() for para in paras if para.text_content()]
        para = random.choice(paras_text)
        para_tokenized = tokenizer.tokenize(para)
        for _ in range(10):
            text = random.choice(para_tokenized)
            if text and 60 < len(text) < 210:
                break

如果您通过打开第一个链接来检查博客文章,您会注意到所有段落都属于 div 标记,其类别为 entry-content。 因此,您使用 paras = blog_tree.xpath('//div[@class="entry-content"]/p') 将所有段落提取为列表。

列表元素不是 literal 段落; 它们是 Element 对象。 要从这些 对象 中提取文本,请使用 text_content() 方法。 这一行遵循 Python 的 列表理解 设计模式,该模式使用通常写在一行中的循环来定义集合。 在 bot.py 中,提取每个段落元素 object 的文本,如果文本不为空,则将其存储在 list 中。 要从这个段落列表中随机选择一个段落,您需要合并 random 模块。

最后,你必须从这一段中随机选择一个句子,它存储在变量para中。 对于此任务,您首先将段落分成句子。 实现此目的的一种方法是使用 Python 的 split() 方法。 然而,这可能很困难,因为一个句子可以在多个断点处拆分。 因此,为了简化拆分任务,您可以通过 nltk 库利用自然语言处理。 您在本教程前面定义的 tokenizer 对象将对此很有用。

现在您有了一个句子列表,您可以调用 random.choice() 来提取一个随机句子。 您希望这句话作为推文的引用,因此不能超过 280 个字符。 但是,出于美学原因,您将选择一个既不太大也不太小的句子。 您指定您的推文句子的长度应在 60 到 210 个字符之间。 句子 random.choice() picks 可能不满足这个标准。 为了识别正确的句子,您的脚本将尝试十次,每次都检查标准。 一旦随机选取的句子满足您的标准,您就可以跳出循环。

虽然概率很低,但有可能在十次尝试中没有一个句子满足这个大小条件。 在这种情况下,您将忽略相应的博客文章并继续下一篇文章。

现在你有一个句子要引用,你可以用相应的链接发推文。 您可以通过生成一个包含随机选取的句子以及相应的博客链接的字符串来做到这一点。 然后,调用此 scrape_coursera() 函数的代码将通过 Twitter 的 API 将生成的字符串发布到 Twitter。

按如下方式扩展您的功能:

鸟/bot.py

...
def scrape_coursera():
    ...
    for link in links:
        ...
        para_tokenized = tokenizer.tokenize(para)
        for _ in range(10):
            text = random.choice(para)
            if text and 60 < len(text) < 210:
                break
        else:
            yield None
        yield '"%s" %s' % (text, link)

该脚本仅在前面的 for 循环没有中断时才执行 else 语句。 因此,只有在循环无法找到适合您的大小条件的句子时才会发生这种情况。 在这种情况下,您只需 yield None 以便调用此函数的代码能够确定没有要发推文的内容。 然后它将继续再次调用该函数并获取下一个博客链接的内容。 但如果循环确实中断,则意味着该函数找到了合适的句子; 该脚本不会执行 else 语句,该函数将产生一个由句子和博客链接组成的字符串,由单个 空格 分隔。

scrape_coursera()函数的实现基本完成了。 如果你想制作一个类似的功能来抓取另一个网站,你将不得不重复你为抓取 Coursera 的博客而编写的一些代码。 为避免重写和复制部分代码并确保您的机器人脚本遵循 DRY 原则(不要重复自己),您将识别并抽象出您将再次使用的部分代码,并再次用于稍后编写的任何刮板功能。

无论该功能正在抓取哪个网站,您都必须随机选取一个段落,然后从该选定段落中选择一个随机句子——您可以在单独的函数中提取这些功能。 然后,您可以简单地从您的爬虫函数中调用这些函数并获得所需的结果。 你也可以在scrape_coursera()函数之外定义HEADERS,这样所有的scraper函数都可以使用它。 因此,在下面的代码中,HEADERS 定义应该先于 scraper 函数的定义,以便最终您可以将其用于其他抓取器:

鸟/bot.py

...
HEADERS = {
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5)'
                  ' AppleWebKit/537.36 (KHTML, like Gecko) Cafari/537.36'
    }


def scrape_coursera():
    r = requests.get('https://blog.coursera.org', headers=HEADERS)
    ...

现在您可以定义 extract_paratext() 函数,用于从段落对象列表中提取随机段落。 随机段落将作为 paras 参数传递给函数,并返回所选段落的标记化形式,您稍后将使用它来提取句子:

鸟/bot.py

...
HEADERS = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5)'
                      ' AppleWebKit/537.36 (KHTML, like Gecko) Cafari/537.36'
        }

def extract_paratext(paras):
    """Extracts text from <p> elements and returns a clean, tokenized random
    paragraph."""

    paras = [para.text_content() for para in paras if para.text_content()]
    para = random.choice(paras)
    return tokenizer.tokenize(para)


def scrape_coursera():
    r = requests.get('https://blog.coursera.org', headers=HEADERS)
    ...

接下来,您将定义一个函数,该函数将从作为参数获取的标记化段落中提取一个合适长度(60 到 210 个字符之间)的随机句子,您可以将其命名为 para。 如果十次尝试后没有发现这样的句子,则函数返回 None。 添加以下突出显示的代码来定义 extract_text() 函数:

鸟/bot.py

...

def extract_paratext(paras):
    ...
    return tokenizer.tokenize(para)


def extract_text(para):
    """Returns a sufficiently-large random text from a tokenized paragraph,
    if such text exists. Otherwise, returns None."""

    for _ in range(10):
        text = random.choice(para)
        if text and 60 < len(text) < 210:
            return text

    return None


def scrape_coursera():
    r = requests.get('https://blog.coursera.org', headers=HEADERS)
    ...

一旦定义了这些新的辅助函数,就可以重新定义 scrape_coursera() 函数,如下所示:

鸟/bot.py

...
def extract_paratext():
    for _ in range(10):<^>
        text = random.choice(para)
    ...


def scrape_coursera():
    """Scrapes content from the Coursera blog."""

    url = 'https://blog.coursera.org'
    r = requests.get(url, headers=HEADERS)
    tree = fromstring(r.content)
    links = tree.xpath('//div[@class="recent"]//div[@class="title"]/a/@href')

    for link in links:
        r = requests.get(link, headers=HEADERS)
        blog_tree = fromstring(r.content)
        paras = blog_tree.xpath('//div[@class="entry-content"]/p')
        para = extract_paratext(paras)
        text = extract_text(para)
        if not text:
            continue

        yield '"%s" %s' % (text, link)

保存并退出bot.py

在这里,您使用的是 yield 而不是 return 因为,为了遍历链接,scraper 函数将以顺序的方式一个接一个地为您提供推文字符串。 这意味着当您第一次调用定义为 sc = scrape_coursera() 的刮板 sc 时,您将获得与您在刮板函数中计算的链接列表中的第一个链接对应的推文字符串。 如果您在解释器中运行以下代码,如果 scrape_coursera() 中的 links 变量包含一个列表,您将得到如下所示的 string_1string_2看起来像 ["https://thenewstack.io/cloud-native-live-twistlocks-virtual-conference/%22, "https://blog.coursera.org/unlock-the-power-of-data-with-python-university-of-michigan-offers-new-programming-specializations-on-coursera/%22, ...]

python3 -i bot.py

实例化刮板并将其命名为sc

>>> sc = scrape_coursera()

它现在是一个生成器; 它从 Coursera 生成或抓取相关内容,一次一个。 您可以通过依次调用 next() 而不是 sc 来逐一访问抓取的内容:

>>> string_1 = next(sc)
>>> string_2 = next(sc)

现在您可以 print 您定义的字符串来显示抓取的内容:

>>> print(string_1)
"Other speakers include Priyanka Sharma, director of cloud native alliances at GitLab and Dan Kohn, executive director of the Cloud Native Computing Foundation." https://thenewstack.io/cloud-native-live-twistlocks-virtual-conference/
>>>
>>> print(string_2)
"You can learn how to use the power of Python for data analysis with a series of courses covering fundamental theory and project-based learning." https://blog.coursera.org/unlock-the-power-of-data-with-python-university-of-michigan-offers-new-programming-specializations-on-coursera/
>>>

如果你使用 return 代替,你将无法一个接一个地按顺序获取字符串。 如果您只是将 scrape_coursera() 中的 yield 替换为 return,您将始终获得与第一篇博文对应的字符串,而不是在第一次调用中获得第一个字符串,第二个电话中的第二个,依此类推。 您可以修改该函数以简单地返回与所有链接对应的所有字符串的 list,但这会占用更多内存。 此外,如果您想要快速获取整个 列表 ,这种程序可能会在短时间内向 Coursera 的服务器发出大量请求。 这可能会导致您的机器人被暂时禁止访问网站。 因此,yield 最适合各种抓取作业,您只需一次抓取一个信息。

第 4 步 — 抓取其他内容

在这一步中,您将为 thenewstack.io 构建一个爬虫。 该过程类似于您在上一步中完成的过程,因此这将是一个快速概述。

在浏览器中打开网站并检查页面源。 您会在这里发现所有博客部分都是 normalstory-box 类的 div 元素。

现在您将创建一个名为 scrape_thenewstack() 的新爬虫函数,并从其中向 thenewstack.io 发出 GET 请求。 接下来,从这些元素中提取指向博客的链接,然后遍历每个链接。 添加以下代码以实现此目的:

鸟/bot.py

...
def scrape_coursera():
    ...
    yield '"%s" %s' % (text, link)


def scrape_thenewstack():
    """Scrapes news from thenewstack.io"""

    r = requests.get('https://thenewstack.io', verify=False)

        tree = fromstring(r.content)
        links = tree.xpath('//div[@class="normalstory-box"]/header/h2/a/@href')
        for link in links:

您使用 verify=False 标志是因为网站有时可能具有过期的安全证书,如果不涉及敏感数据,则可以访问它们,就像这里的情况一样。 verify=False 标志告诉 requests.get 方法不要验证证书并像往常一样继续获取数据。 否则,该方法会引发有关过期安全证书的错误。

您现在可以提取每个链接对应的博客段落,并使用您在上一步中构建的 extract_paratext() 函数从可用段落列表中随机抽取一个段落。 最后,使用extract_text()函数从这一段中抽取一个随机句子,然后用相应的博客链接yield它。 将以下突出显示的代码添加到您的文件中以完成这些任务:

鸟/bot.py

...
def scrape_thenewstack():
    ...
    links = tree.xpath('//div[@class="normalstory-box"]/header/h2/a/@href')

    for link in links:
        r = requests.get(link, verify=False)
        tree = fromstring(r.content)
        paras = tree.xpath('//div[@class="post-content"]/p')
        para = extract_paratext(paras)
        text = extract_text(para)  
        if not text:
            continue

        yield '"%s" %s' % (text, link)

您现在已经了解了抓取过程通常包含的内容。 您现在可以构建自己的自定义抓取工具,例如,可以抓取博客文章中的图像,而不是随机引用。 为此,您可以查找相关的 <img> 标签。 一旦你有了标签的正确路径,作为它们的标识符,你可以使用相应属性的名称访问标签内的信息。 例如,在抓取图像的情况下,您可以使用图像的 src 属性访问图像的链接。

至此,您已经构建了两个用于从两个不同网站抓取内容的爬虫函数,并且您还构建了两个辅助函数来重用这两个爬虫通用的功能。 现在您的机器人知道如何发推文和发什么推文,您将编写代码来推文抓取的内容。

第 5 步 - 在推特上发布抓取的内容

在此步骤中,您将扩展机器人以从两个网站上抓取内容并通过您的 Twitter 帐户发布推文。 更准确地说,您希望它以十分钟的固定间隔交替发布来自两个网站的内容,并无限期地发布。 因此,您将使用 无限循环 来实现所需的功能。 您将作为 main() 函数的一部分执行此操作,该函数将实现您希望机器人遵循的核心高级流程:

鸟/bot.py

...
def scrape_thenewstack():
    ...
    yield '"%s" %s' % (text, link)


def main():
    """Encompasses the main loop of the bot."""
    print('---Bot started---\n')
    news_funcs = ['scrape_coursera', 'scrape_thenewstack']
    news_iterators = []  
    for func in news_funcs:
        news_iterators.append(globals()[func]())
    while True:
        for i, iterator in enumerate(news_iterators):
            try:
                tweet = next(iterator)
                t.statuses.update(status=tweet)
                print(tweet, end='\n\n')
                time.sleep(600)  
            except StopIteration:
                news_iterators[i] = globals()[newsfuncs[i]]()

您首先创建一个您之前定义的抓取函数名称的列表,并将其称为 news_funcs。 然后创建一个空列表来保存实际的爬虫函数,并将该列表命名为 news_iterators。 然后通过遍历 news_funcs 列表中的每个名称并在 news_iterators 列表中附加相应的迭代器来填充它。 您正在使用 Python 的内置 globals() 函数。 这将返回一个字典,将变量名称映射到脚本中的实际变量。 迭代器是你调用爬虫函数时得到的:例如,如果你写 coursera_iterator = scrape_coursera(),那么 coursera_iterator 将是一个迭代器,你可以在其上调用 next() 调用。 每个 next() 调用将返回一个包含引号及其对应链接的字符串,与 scrape_coursera() 函数的 yield 语句中定义的完全一样。 每个 next() 调用都经过 scrape_coursera() 函数中的 for 循环的一次迭代。 因此,您只能进行与 scrape_coursera() 函数中的博客链接一样多的 next() 调用。 一旦超过该数字,将引发 StopIteration 异常。

一旦两个迭代器都填充了 news_iterators 列表,主 while 循环就会开始。 在其中,您有一个 for 循环,它遍历每个迭代器并尝试获取要发布的内容。 获取内容后,您的机器人会发布推文,然后休眠十分钟。 如果迭代器没有更多内容可提供,则会引发 StopIteration 异常,然后通过重新实例化迭代器来刷新它,以检查源网站上更新内容的可用性。 然后你继续下一个迭代器,如果有的话。 否则,如果执行到达迭代器列表的末尾,您将从头重新开始并推特下一个可用内容。 这使您的机器人在两个刮板中交替发布内容,只要您愿意。

现在剩下的就是调用 main() 函数。 当 Python 解释器直接调用脚本 ' 时,您可以这样做:

鸟/bot.py

...
def main():
    print('---Bot started---\n')<^>
    news_funcs = ['scrape_coursera', 'scrape_thenewstack']
    ...

if __name__ == "__main__":  
    main()

以下是bot.py脚本的完整版。 您还可以在此 GitHub 存储库 上查看 脚本。

鸟/bot.py

"""Main bot script - bot.py
For the DigitalOcean Tutorial.
"""


import random
import time


from lxml.html import fromstring
import nltk  
nltk.download('punkt')
import requests  

from twitter import OAuth, Twitter


import credentials

tokenizer = nltk.data.load('tokenizers/punkt/english.pickle')

oauth = OAuth(
        credentials.ACCESS_TOKEN,
        credentials.ACCESS_SECRET,
        credentials.CONSUMER_KEY,
        credentials.CONSUMER_SECRET
    )
t = Twitter(auth=oauth)

HEADERS = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5)'
                      ' AppleWebKit/537.36 (KHTML, like Gecko) Cafari/537.36'
        }


def extract_paratext(paras):
    """Extracts text from <p> elements and returns a clean, tokenized random
    paragraph."""

    paras = [para.text_content() for para in paras if para.text_content()]
    para = random.choice(paras)
    return tokenizer.tokenize(para)


def extract_text(para):
    """Returns a sufficiently-large random text from a tokenized paragraph,
    if such text exists. Otherwise, returns None."""

    for _ in range(10):
        text = random.choice(para)
        if text and 60 < len(text) < 210:
            return text

    return None


def scrape_coursera():
    """Scrapes content from the Coursera blog."""
    url = 'https://blog.coursera.org'
    r = requests.get(url, headers=HEADERS)
    tree = fromstring(r.content)
    links = tree.xpath('//div[@class="recent"]//div[@class="title"]/a/@href')

    for link in links:
        r = requests.get(link, headers=HEADERS)
        blog_tree = fromstring(r.content)
        paras = blog_tree.xpath('//div[@class="entry-content"]/p')
        para = extract_paratext(paras)  
        text = extract_text(para)  
        if not text:
            continue

        yield '"%s" %s' % (text, link)  


def scrape_thenewstack():
    """Scrapes news from thenewstack.io"""

    r = requests.get('https://thenewstack.io', verify=False)

    tree = fromstring(r.content)
    links = tree.xpath('//div[@class="normalstory-box"]/header/h2/a/@href')

    for link in links:
        r = requests.get(link, verify=False)
        tree = fromstring(r.content)
        paras = tree.xpath('//div[@class="post-content"]/p')
        para = extract_paratext(paras)
        text = extract_text(para)  
        if not text:
            continue

        yield '"%s" %s' % (text, link)


def main():
    """Encompasses the main loop of the bot."""
    print('Bot started.')
    news_funcs = ['scrape_coursera', 'scrape_thenewstack']
    news_iterators = []  
    for func in news_funcs:
        news_iterators.append(globals()[func]())
    while True:
        for i, iterator in enumerate(news_iterators):
            try:
                tweet = next(iterator)
                t.statuses.update(status=tweet)
                print(tweet, end='\n')
                time.sleep(600)
            except StopIteration:
                news_iterators[i] = globals()[newsfuncs[i]]()


if __name__ == "__main__":  
    main()

保存并退出bot.py

以下是 bot.py 的示例执行:

python3 bot.py

您将收到显示您的机器人已抓取的内容的输出,格式类似于以下内容:

Output[nltk_data] Downloading package punkt to /Users/binaryboy/nltk_data...
[nltk_data]   Package punkt is already up-to-date!
---Bot started---

"Take the first step toward your career goals by building new skills." https://blog.coursera.org/career-stories-from-inside-coursera/

"Other speakers include Priyanka Sharma, director of cloud native alliances at GitLab and Dan Kohn, executive director of the Cloud Native Computing Foundation." https://thenewstack.io/cloud-native-live-twistlocks-virtual-conference/

"You can learn how to use the power of Python for data analysis with a series of courses covering fundamental theory and project-based learning." https://blog.coursera.org/unlock-the-power-of-data-with-python-university-of-michigan-offers-new-programming-specializations-on-coursera/

"“Real-user monitoring is really about trying to understand the underlying reasons, so you know, ‘who do I actually want to fly with?" https://thenewstack.io/how-raygun-co-founder-and-ceo-spun-gold-out-of-monitoring-agony/

在您的机器人运行示例之后,您将在您的 Twitter 页面上看到您的机器人发布的程序化推文的完整时间线。 它将如下所示:

如您所见,该机器人在推特上将抓取的博客链接与每个博客的随机引用作为亮点。 这个提要现在是一个信息提要,推文在 Coursera 和 thenewstack.io 的博客引用之间交替出现。 您已经构建了一个机器人,它可以聚合来自网络的内容并将其发布在 Twitter 上。 现在,您可以根据自己的意愿通过为不同的网站添加更多抓取工具来扩大此机器人的范围,并且该机器人将以循环方式和您想要的时间间隔发布来自所有抓取工具的内容。

结论

在本教程中,您使用 Python 构建了一个基本的 Twitter 机器人,并从网络上抓取了一些内容供您的机器人发布推文。 有很多机器人想法可以尝试; 您还可以为机器人的实用程序实现自己的想法。 您可以结合 Twitter API 提供的多种功能并创建更复杂的东西。 对于更复杂的 Twitter 机器人版本,请查看 chirps,这是一个 Twitter 机器人框架,它使用多线程等一些高级概念使机器人同时执行多项操作。 还有一些有趣的想法机器人,例如 听错了。 在构建 Twitter 机器人时可以使用的创造力没有限制。 为你的机器人的实现找到合适的 API 端点是必不可少的。

最后,在构建下一个机器人时要牢记机器人礼仪或(“botiquette”)很重要。 例如,如果您的机器人包含转推功能,请在转推之前让所有推文的文本通过过滤器以检测辱骂性语言。 您可以使用正则表达式和自然语言处理来实现这些功能。 此外,在寻找要抓取的来源时,请遵循您的判断并避免传播错误信息的来源。 要阅读有关 botiquette 的更多信息,您可以访问 这篇由 Joe Mayo 撰写的关于该主题的博文