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

Twitter机器人是管理社交媒体以及从微博网络中提取信息的有效方式。在本教程中,您将使用这个用于Python的Twitter API库构建一个Twitter机器人。您将对您的机器人进行编程,以便按照设定的时间间隔来交替发布来自这两个网站的内容。

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

介绍

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

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

先决条件

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

注意:您将在Twitter上设置开发人员帐户,其中包括Twitter的应用程序审核,然后您才能访问此僵尸程序所需的API密钥。 第1步介绍完成应用程序的具体细节。

第1步 - 设置开发人员帐户并访问Twitter API密钥

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

要获取API密钥,请访问developer.twitter.com并通过单击页面右上角的“应用”向Twitter注册您的bot应用程序。

现在点击申请开发者帐户

接下来,单击继续将您的Twitter用户名与您将在本教程中构建的bot应用程序相关联。

Twitter用户名与Bot的关联

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

Twitter API个人使用

选择您的帐户名称国家/地区后 ,请转到下一部分。 您对哪些用例感兴趣? ,选择发布和策划推文学生项目/学习编码选项。 这些类别是您完成本教程的最佳代表。

Twitter Bot目的

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

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

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

最后,选择您的产品,服务或分析是否会将Twitter内容或衍生信息提供给政府实体?

Twitter Bot意图

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

验证完电子邮件后,您将收到一份正在审核申请表,其中包含申请流程的反馈表。

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

申请审核电邮

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

最后,转到应用程序页面上的“ 权限”选项卡,并将“ 访问权限”选项设置为“ 读取和写入”,因为您还要编写推文内容。 通常,您将使用只读模式进行研究,例如分析趋势,数据挖掘等。 最终选项允许用户将聊天机器人集成到他们现有的应用程序中,因为聊天机器人需要访问直接消息。

Twitter应用程序权限页面

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

第2步 - 构建Essentials

在此步骤中,您将使用API​​密钥编写代码以使用Twitter对您的机器人进行身份验证,并通过您的Twitter手柄发出第一条程序性推文。 这将是您实现构建Twitter机器人的目标的一个重要里程碑,该机器人会从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中的密钥替换突出显示的代码:

鸟/ credentials.py

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

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

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

pip3 install lxml nltk requests twitter
  • lxmlrequests :您将使用它们进行网页抓取。
  • twitter :这是用于向Twitter的服务器进行API调用的库。
  • nltk :(自然语言工具包)您将使用将博客段落分成句子。
  • random :您将使用它随机选择整个已删除博客文章的部分内容。
  • time :您将在某些动作后定期让您的机器人睡觉。

一旦安装了库,就可以开始编程了。 现在,您将把凭据导入到运行bot的主脚本中。 除了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函数将已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

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

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

如果您收到错误,请使用Twitter开发者帐户中的 API密钥验证已保存的API密钥,然后重试。 还要确保正确安装了所需的库。 如果没有, pip3再次使用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结构。 结构将允许您方便地访问网页的不同部分。 HTML源代码具有特定的树状结构; 每个元素都包含在<html>标记中,然后嵌套。

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

浏览器检查

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

博客-DIV

因此,在您的代码中,您将通过XPath使用所有此类博客post 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元素, 类是 "recent" //对应于搜索整个网页, div告诉函数只提取div元素, [@class="recent"]要求它只提取那些具有class属性值为"recent" div元素。

但是,您自己不需要这些元素,只需要他们指向的链接,这样您就可以访问各个博客文章来删除其内容。 因此,您使用博客帖子的前一个div标记内的href锚标记的值来提取所有链接。

到目前为止测试你的程序,你可以在scrape_coursera()的末尾调用scrape_coursera()函数。

保存并退出bot.py

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

python3 bot.py

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

['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)
            if text and 60 < len(text) < 210:
                break

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

Div Enclosing Paragraphs

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

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

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

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

现在您有一个引用的句子,您可以使用相应的链接发布它。 您可以通过生成包含随机拾取的句子以及相应的博客链接的字符串来完成此操作。 调用此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语句。 因此,它只发生在循环无法找到符合您的大小条件的句子时。 在这种情况下,您只需生成None以便调用此函数的代码能够确定没有任何推文。 然后它将继续再次调用该函数并获取下一个博客链接的内容。 但是如果循环确实破坏了意味着函数找到了一个合适的句子; 该脚本不会执行else语句,该函数将生成一个由句子和博客链接组成的字符串,由单个空格分隔。

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

无论网站功能是什么,你都必须随机选择一个段落,然后从这个选定的段落中选择一个随机句子 - 你可以在不同的函数中提取出这些功能。 然后,您只需从刮刀功能中调用这些功能,即可获得所需的结果。 您还可以在scrape_coursera()函数之外定义HEADERS ,以便所有scraper函数都可以使用它。 因此,在下面的代码中, HEADERS定义应该先于scraper函数的定义,因此最终您可以将它用于其他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()的scraper sc ,您将获得与您在scraper函数中计算的链接列表中的第一个链接对应的tweet字符串。 如果你在解释器中运行以下代码,如果scrape_coursera()links变量包含一个看起来像["https://thenewstack.io/cloud-native-live-twistlocks-virtual-conference/", "https://blog.coursera.org/unlock-the-power-of-data-with-python-university-of-michigan-offers-new-programming-specializations-on-coursera/", ...]的列表,你将得到如下所示的string_1string_2 ["https://thenewstack.io/cloud-native-live-twistlocks-virtual-conference/", "https://blog.coursera.org/unlock-the-power-of-data-with-python-university-of-michigan-offers-new-programming-specializations-on-coursera/", ...]

python3 -i bot.py

实例化刮刀并将其称为sc

>>> sc = scrape_coursera()

它现在是一个Generators; 它会一次一个地生成或删除Coursera中的相关内容。 您可以通过按顺序调用next()来逐个访问已删除的内容:

>>> 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() return替换yield ,你将总是获得与第一篇博文相对应的字符串,而不是第一次调用中的第一个,第二次调用中的第二个,依此类推。 您可以修改该函数以简单地返回与所有链接相对应的所有字符串的列表 ,但这会占用更多内存。 此外,如果您想快速获得整个列表 ,这种程序可能会在很短的时间内向Coursera的服务器发出大量请求。 这可能会导致您的机器人暂时被禁止访问网站。 因此, yield最适合各种刮削工作,您只需要一次一个地刮取信息。

第4步 - 刮擦其他内容

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

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

新Stack网站的HTML源检查

现在,您将创建一个名为scrape_thenewstack()的新scraper函数, 从其中向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()函数从该段中提取一个随机句子,然后使用相应的博客链接生成它。 将以下突出显示的代码添加到您的文件中以完成以下任务:

鸟/ 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属性访问图像链接。

此时,您已经构建了两个用于从两个不同网站抓取内容的scraper函数,并且您还构建了两个辅助函数来重用两个刮刀中常见的功能。 现在您的机器人知道如何发推文和推文,您将编写代码来发布已删除的内容。

第5步 - 推特已删除的内容

在此步骤中,您将扩展机器人以从两个网站上抓取内容并通过您的Twitter帐户发送。 更确切地说,您希望它可以无限期地交替发布来自两个网站的内容,并定期发布十分钟。 因此,您将使用无限while循环来实现所需的功能。 您将在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 然后创建一个空列表,其中包含实际的scraper函数,并将该列表命名为news_iterators 然后,您可以通过遍历news_funcs列表中的每个名称并在news_funcs列表中附加相应的迭代器来填充它。 您正在使用Python的内置globals()函数。 这将返回一个字典,用于将变量名称映射到脚本中的实际变量。 调用刮刀函数时会得到迭代器:例如,如果编写coursera_iterator = scrape_coursera() ,则coursera_iterator将是一个迭代器,您可以在其上调用next()调用。 每个next()调用都将返回一个包含引号及其对应链接的字符串,与scrape_coursera()函数的yield语句中的定义完全相同。 每个next()调用都经过scrape_coursera()函数中for循环的一次迭代。 因此,您只能进行与scrape_coursera()函数中的博客链接一样多的next()调用。 一旦该数量超过,将引发StopIteration异常。

一旦两个迭代器都填充了news_iterators列表,就会启动main 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

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

[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/

After a sample run of your bot, you'll see a full timeline of programmatic tweets posted by your bot on your Twitter page. It will look something like the following:

Programmatic Tweets posted

As you can see, the bot is tweeting the scraped blog links with random quotes from each blog as highlights. This feed is now an information feed with tweets alternating between blog quotes from Coursera and thenewstack.io. You've built a bot that aggregates content from the web and posts it on Twitter. You can now broaden the scope of this bot as per your wish by adding more scrapers for different websites, and the bot will tweet content coming from all the scrapers in a round-robin fashion, and in your desired time intervals.

结论

In this tutorial you built a basic Twitter bot with Python and scraped some content from the web for your bot to tweet. There are many bot ideas to try; you could also implement your own ideas for a bot's utility. You can combine the versatile functionalities offered by Twitter's API and create something more complex. For a version of a more sophisticated Twitter bot, check out chirps , a Twitter bot framework that uses some advanced concepts like multithreading to make the bot do multiple things simultaneously. There are also some fun-idea bots, like misheardly . There are no limits on the creativity one can use while building Twitter bots. Finding the right API endpoints to hit for your bot's implementation is essential.

Finally, bot etiquette or ("botiquette") is important to keep in mind when building your next bot. For example, if your bot incorporates retweeting, make all tweets' text pass through a filter to detect abusive language before retweeting them. You can implement such features using regular expressions and natural language processing. Also, while looking for sources to scrape, follow your judgment and avoid ones that spread misinformation. To read more about botiquette, you can visit this blog post by Joe Mayo on the topic.


分享按钮