Как чистить веб-страницы и публиковать контент в Twitter с помощью Python 3

Автор выбралThe Computer History Museum для получения пожертвования в рамках программыWrite for DOnations.

Вступление

БотыTwitter - это мощный способ управления вашими социальными сетями, а также извлечения информации из сети микроблогов. Используя универсальные API Twitter, бот может делать много вещей: твит, ретвит, «любимый твит», следить за людьми с определенными интересами, автоматически отвечать и так далее. Несмотря на то, что люди могут злоупотреблять властью своего бота, что приводит к негативным последствиям для других пользователей, исследования показывают, что люди считают ботов Twitter надежным источником информации. Например, бот может поддерживать содержание ваших подписчиков даже в том случае, если вы не в сети. Некоторые боты даже предоставляют важную и полезную информацию, например@EarthquakesSF. Приложения для ботов безграничны. По оценкам, на 2019 год боты составляют около24% of all tweets в Twitter.

В этом руководстве вы создадите бота Twitter, используяthis Twitter API library для Python. Вы будете использовать ключи API из своей учетной записи Twitter, чтобы авторизовать своего бота и создать систему, способную собирать содержимое с двух сайтов. Кроме того, вы будете программировать своего бота поочередно чирикать контент с этих двух сайтов и через определенные промежутки времени. Обратите внимание, что вы будете использовать Python 3 в этом руководстве.

Предпосылки

Для выполнения этого урока вам понадобится следующее:

[.note] #Note: Вы создадите учетную запись разработчика в Twitter, что включает в себя проверку приложения в Twitter, прежде чем вы сможете получить доступ к ключам API, необходимым для этого бота. На шаге 1 рассматриваются конкретные детали для заполнения заявки.
#

[[step-1 -—- setting-up-your-developer-account-and-accessing-your-twitter-api-keys]] == Шаг 1. Настройка вашей учетной записи разработчика и доступ к вашим ключам API Twitter

Прежде чем вы начнете кодировать своего бота, вам понадобятся ключи API для Twitter, чтобы распознавать запросы вашего бота. На этом этапе вы настроите свою учетную запись разработчика Twitter и получите доступ к своим ключам API для своего бота Twitter.

Чтобы получить ключи API, перейдите вdeveloper.twitter.com и зарегистрируйте свое бот-приложение в Twitter, щелкнувApply в правом верхнем углу страницы.

Теперь нажмитеApply for a developer account.

Затем нажмитеContinue, чтобы связать свое имя пользователя Twitter с вашим бот-приложением, которое вы создадите в этом руководстве.

Twitter Username Association with Bot

На следующей странице для целей этого руководства вы выберете опциюI am requesting access for my own personal use, так как вы будете создавать бота для личного обучения.

Twitter API Personal Use

ВыбравAccount Name иCountry, переходите к следующему разделу. ДляWhat use case(s) are you interested in? выберите параметрыPublish and curate Tweets иStudent project / Learning to code. Эти категории - лучшее представление о том, почему вы заканчиваете этот урок.

Twitter Bot Purpose

Затем предоставьте описание бота, которого вы пытаетесь создать. Twitter требует этого для защиты от злоупотреблений ботами; в 2018 году они ввели такую ​​проверку. В этом руководстве вы будете извлекать технический контент изThe New Stack иThe Coursera Blog.

Решая, что ввести в полеdescription, смоделируйте свой ответ на следующих строках для целей этого руководства:

Я следую руководству по созданию бота Twitter, который будет извлекать контент с таких сайтов, как thenewstack.io (Новый стек) и blog.coursera.org (Блог Coursera), и размещать их цитаты в Твиттере. Полученный контент будет агрегирован и публиковаться в твиттере циклически с помощью функций генератора Python.

Наконец, выберитеno вместоWill your product, service, or analysis make Twitter content or derived information available to a government entity?

Twitter Bot Intent

Затем примите условия использования Twitter, нажмитеSubmit application и подтвердите свой адрес электронной почты. Twitter отправит вам письмо с подтверждением после отправки этой формы.

Подтвердив свой адрес электронной почты, вы получите страницуApplication under review с формой обратной связи для процесса подачи заявки.

Вы также получите еще одно электронное письмо от Twitter относительно обзора:

Application Review Email

Сроки рассмотрения заявки в Твиттере могут значительно различаться, но часто Твиттер подтверждает это в течение нескольких минут. Однако, если рассмотрение вашего заявления занимает больше времени, это не является необычным, и вы должны получить его в течение дня или двух. Как только вы получите подтверждение, Twitter разрешит вам создать ваши ключи. Вы можете получить к ним доступ на вкладкеKeys and tokens после нажатия кнопки сведений вашего приложения наdeveloper.twitter.com/apps.

Наконец, перейдите на вкладкуPermissions на странице вашего приложения и установите для параметраAccess Permission значениеRead and Write, поскольку вы тоже хотите писать твит-контент. Обычно вы используете режим «только чтение» для исследовательских целей, таких как анализ тенденций, анализ данных и так далее. Последний вариант позволяет пользователям интегрировать чат-ботов в свои существующие приложения, поскольку чат-боты требуют доступа к прямым сообщениям.

Twitter App Permissions Page

У вас есть доступ к мощному API Twitter, который станет важной частью вашего бот-приложения. Теперь вы настроите свою среду и начнете создавать своего бота.

[[step-2 -—- building-the-essentials]] == Шаг 2. Создание основ

На этом шаге вы напишите код для аутентификации вашего бота с помощью Twitter с помощью ключей API и создадите первый программный твит с помощью своего дескриптора Twitter. Это станет хорошей вехой на вашем пути к цели создания бота Twitter, который извлекает контент изThe New Stack иCoursera Blog и периодически публикует их в Твиттере.

Во-первых, вы настроите папку проекта и определенную среду программирования для вашего проекта.

Создайте папку вашего проекта:

mkdir bird

Переместитесь в папку вашего проекта:

cd bird

Затем создайте новую виртуальную среду Python для своего проекта:

python3 -m venv bird-env

Затем активируйте свою среду, используя следующую команду:

source bird-env/bin/activate

Это добавит префикс(bird-env) к приглашению в окне терминала.

Теперь перейдите в текстовый редактор и создайте файл с именемcredentials.py, в котором будут храниться ваши ключи API Twitter:

nano credentials.py

Добавьте следующий контент, заменив выделенный код вашими ключами из Twitter:

bird/credentials.py

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

Теперь вы установите основную библиотеку API для отправки запросов в Twitter. Для этого проекта вам потребуются следующие библиотеки:nltk,requests,twitter,lxml,random иtime. random иtime являются частью стандартной библиотеки Python, поэтому вам не нужно отдельно устанавливать эти библиотеки. Чтобы установить оставшиеся библиотеки, вы воспользуетесьpip, менеджером пакетов для Python.

Откройте свой терминал, убедитесь, что вы находитесь в папке проекта, и выполните следующую команду:

pip3 install lxml nltk requests twitter
  • lxml иrequests: вы будете использовать их для очистки веб-страниц.

  • twitter: это библиотека для выполнения вызовов API к серверам Twitter.

  • nltk: (набор инструментов для естественного языка) Вы будете использовать для разделения абзацев блога на предложения.

  • random: вы будете использовать это для случайного выбора частей всего очищенного сообщения в блоге.

  • time: вы будете использовать, чтобы ваш бот периодически засыпал после определенных действий.

После того, как вы установили библиотеки, у вас все готово для начала программирования. Теперь вы импортируете свои учетные данные в основной скрипт, который будет запускать бот. Вместе сcredentials.py в текстовом редакторе создайте файл в каталоге проектаbird и назовите егоbot.py:

nano bot.py

На практике вы будете распределять функциональность своего бота по нескольким файлам, поскольку он становится все более изощренным. Однако в этом руководстве вы поместите весь свой код в один скриптbot.py для демонстрационных целей.

Сначала вы протестируете свои ключи API, авторизовав бота. Начните с добавления следующего фрагмента вbot.py:

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

Здесь вы импортируете необходимые библиотеки; и в нескольких случаях вы импортируете необходимыеfunctions из библиотек. Позже вы будете использовать функциюfromstring в коде, чтобы преобразовать строковый источник очищенной веб-страницы в древовидную структуру, которая упрощает извлечение соответствующей информации со страницы. OAuth поможет вам создать объект аутентификации из ваших ключей, аTwitter создаст основной объект API для всей дальнейшей связи с серверами Twitter.

Теперь расширитеbot.py следующими строками:

bird/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_TOKEN иACCESS_SECRET помогают распознать ваше приложение. Наконец,CONSUMER_KEY иCONSUMER_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!

Если вы все же получаете сообщение об ошибке, сверьте сохраненные ключи API с ключами вTwitter developer account и повторите попытку. Также убедитесь, что необходимые библиотеки установлены правильно. Если нет, снова используйтеpip3, чтобы установить их.

Теперь вы можете попробовать написать что-то программно. Введите ту же команду на терминале с флагом-i, чтобы открыть интерпретатор Python после выполнения вашего скрипта:

python3 -i bot.py

Затем введите следующее, чтобы отправить твит через свою учетную запись:

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

Теперь откройте свою временную шкалу Twitter в браузере, и вы увидите твит в верхней части своей временной шкалы, содержащий опубликованный вами контент.

First Programmatic Tweet

Закройте интерпретатор, набравquit() илиCTRL + D.

Ваш бот теперь имеет фундаментальную возможность твитнуть. Чтобы разработать своего бота, чтобы твитнуть полезный контент, на следующем шаге вы включите веб-очистку.

[[step-3 -—- scraping-sites-for-your-tweet-content]] == Шаг 3. Очистка веб-сайтов на предмет содержания вашего твита

Чтобы добавить более интересный контент на вашу временную шкалу, вы очистите контент отthe New Stack иCoursera Blog, а затем разместите его в Twitter в форме твитов. Как правило, чтобы собрать нужные данные с ваших целевых веб-сайтов, вы должны поэкспериментировать с их HTML-структурой. Каждый твит от бота, который вы создадите в этом руководстве, будет содержать ссылку на пост в блоге с выбранных сайтов, а также случайную цитату из этого блога. Эту процедуру вы реализуете в функции, предназначенной для извлечения содержимого из Coursera, поэтому назовите ееscrape_coursera().

Первый открытыйbot.py:

nano bot.py

Добавьте функциюscrape_coursera() в конец файла:

bird/bot.py

...
t = Twitter(auth=oauth)


def scrape_coursera():

Чтобы собрать информацию из блога, вы сначала запросите соответствующую веб-страницу с серверов Coursera. Для этого вы воспользуетесь функциейget() из библиотекиrequests. get() принимает URL-адрес и выбирает соответствующую веб-страницу. Итак, вы передадитеblog.coursera.org в качестве аргументаget(). Но вам также необходимо указать заголовок в вашем запросе GET, который позволит серверам Coursera распознать вас как подлинного клиента. Добавьте следующие выделенные строки в функциюscrape_coursera(), чтобы обеспечить заголовок:

bird/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'
        }

Этот заголовок будет содержать информацию, относящуюся к определенному веб-браузеру, работающему в конкретной операционной системе. Поскольку эта информация (обычно обозначаемая какUser-Agent) соответствует реальным веб-браузерам и операционным системам, не имеет значения, соответствует ли информация заголовка фактическому веб-браузеру и операционной системе на вашем компьютере. Поэтому этот заголовок будет хорошо работать для всех систем.

После того, как вы определили заголовки, добавьте следующие выделенные строки, чтобы сделать запрос GET для Coursera, указав URL-адрес веб-страницы блога:

bird/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. Вы можете оценить исходный HTML-код веб-страницы, используя атрибутcontent дляr. Следовательно, значениеr.content такое же, как и то, что вы видите, когда просматриваете веб-страницу в своем браузере, щелкнув страницу правой кнопкой мыши и выбрав параметрInspect Element.

Здесь вы также добавили функциюfromstring. Вы можете передать исходный код веб-страницы в функциюfromstring, импортированную из библиотекиlxml, чтобы построить структуруtree веб-страницы. Эта структураtree позволит вам удобно получать доступ к различным частям веб-страницы. Исходный код HTML имеет особую древовидную структуру; каждый элемент заключен в тег<html> и вложен после него.

Теперь откройтеhttps://blog.coursera.org в браузере и проверьте исходный код HTML с помощью инструментов разработчика браузера. Щелкните страницу правой кнопкой мыши и выберите параметрInspect Element. В нижней части браузера появится окно, отображающее часть исходного кода HTML страницы.

browser-inspect

Затем щелкните правой кнопкой мыши на миниатюре любого видимого сообщения в блоге и просмотрите его. Источник HTML будет подсвечивать соответствующие строки HTML, в которых определен этот эскиз блога. Вы заметите, что все сообщения в блоге на этой странице определены в теге<div> сclass из"recent":

blog-div

Таким образом, в вашем коде вы будете использовать все такие элементыdiv в блоге через ихXPath, что является удобным способом адресации элементов веб-страницы.

Для этого расширите вашу функцию вbot.py следующим образом:

bird/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"recent". // соответствует поиску по всей веб-странице,div указывает функции извлекать только элементыdiv, а[@class="recent"] запрашивает извлечение только этих элементовdiv которые имеют значения своего атрибутаclass как"recent".

Однако сами эти элементы вам не нужны, вам нужны только те ссылки, на которые они указывают, чтобы вы могли получить доступ к отдельным сообщениям в блоге и просмотреть их содержимое. Следовательно, вы извлекаете все ссылки, используя значения тегов привязкиhref, которые находятся в предыдущих тегахdiv сообщений блога.

Чтобы протестировать вашу программу на данный момент, вы вызываете функциюscrape_coursera() в концеbot.py.

Сохраните и выйдите изbot.py.

Теперь запуститеbot.py с помощью следующей команды:

python3 bot.py

На выходе вы увидитеlist URL-адресов, подобных следующему:

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:

bird/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 с помощью следующей выделенной строки, чтобы извлечь содержимое из сообщения в блоге:

bird/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 для его ссылки.

Теперь, когда у вас есть доступ к содержимому блога, вы познакомитесь с кодом, который выполняет эти три шага, чтобы извлечь из него нужный вам контент. Добавьте следующее расширение в вашу функцию очистки, которая выполняет три шага:

bird/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').

Div Enclosing Paragraphs

Элементы списка не являются абзацамиliteral; этоElementobjects. Чтобы извлечь текст из этихobjects, вы используете методtext_content(). Эта строка соответствует шаблону проектирования Pythonlist comprehension, который определяет коллекцию с помощью цикла, который обычно записывается в одну строку. Вbot.py вы извлекаете текст для каждого элемента абзацаobject и сохраняете его вlist, если текст не пуст. Чтобы случайным образом выбрать абзац из этого списка абзацев, вы включаете модульrandom.

Наконец, вы должны случайным образом выбрать предложение из этого абзаца, которое хранится в переменнойpara. Для этой задачи сначала разбейте абзац на предложения. Один из подходов к достижению этого - использование метода Pythonsplit(). Однако это может быть сложно, поскольку предложение может быть разбито на несколько точек останова. Поэтому, чтобы упростить задачи разделения, вы используете обработку естественного языка с помощью библиотекиnltk. Для этой цели будет полезен объектtokenizer, который вы определили ранее в руководстве.

Теперь, когда у вас есть список предложений, вы вызываетеrandom.choice(), чтобы извлечь случайное предложение. Вы хотите, чтобы это предложение было цитатой для твита, поэтому оно не может превышать 280 символов. Однако по эстетическим соображениям вы выберете предложение, которое не является ни слишком большим, ни слишком маленьким. Вы указываете, что ваше предложение в твиттере должно иметь длину от 60 до 210 символов. Предложениеrandom.choice() picks может не удовлетворять этому критерию. Чтобы определить правильное предложение, ваш скрипт сделает десять попыток, каждый раз проверяя критерий. Как только случайно выбранное предложение удовлетворяет вашему критерию, вы можете выйти из цикла.

Хотя вероятность довольно мала, возможно, что ни одно из предложений не соответствует этому условию размера в течение десяти попыток. В этом случае вы проигнорируете соответствующий пост в блоге и перейдете к следующему.

Теперь, когда у вас есть предложение процитировать, вы можете написать его с соответствующей ссылкой. Вы можете сделать это, получив строку, которая содержит случайно выбранное предложение, а также соответствующую ссылку в блоге. Код, вызывающий эту функциюscrape_coursera(), затем отправит полученную строку в Twitter через API Twitter.

Расширьте свою функцию следующим образом:

bird/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)

Сценарий выполняет операторelse, только когда предыдущий циклfor не прерывается. Таким образом, это происходит только тогда, когда цикл не может найти предложение, которое соответствует вашему условию размера. В этом случае вы просто передаетеNone, чтобы код, вызывающий эту функцию, мог определить, что твитнуть нечего. Затем он перейдет к повторному вызову функции и получит содержимое для следующей ссылки в блоге. Но если цикл прерывается, это означает, что функция нашла подходящее предложение; сценарий не выполнит операторelse, а функция выдаст строку, состоящую из предложения, а также ссылку на блог, разделенную однимwhitespace.

Реализация функцииscrape_coursera() практически завершена. Если вы хотите создать подобную функцию для очистки другого веб-сайта, вам придется повторить часть кода, который вы написали для очистки блога Coursera. Чтобы избежать переписывания и дублирования частей кода и гарантировать, что сценарий вашего бота следует принципуDRY (Не повторяйтесь), вы будете идентифицировать и абстрагироваться от частей кода, которые вы будете использовать снова и снова для любая функция скребка, написанная позже.

Независимо от веб-сайта, который функция скрапирует, вам придется случайным образом выбрать абзац, а затем выбрать случайное предложение из этого выбранного абзаца - вы можете выделить эти функции в отдельных функциях. Затем вы можете просто вызвать эти функции из своих функций скребка и достичь желаемого результата. Вы также можете определитьHEADERS вне функцииscrape_coursera(), чтобы все функции скребка могли использовать его. Следовательно, в следующем коде определениеHEADERS должно предшествовать определению функции парсера, чтобы в конечном итоге вы могли использовать его для других парсеров:

bird/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 и вернет токенизированную форму выбранного абзаца, которую вы позже будете использовать для извлечения предложения:

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

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():

bird/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(), чтобы она выглядела следующим образом:

bird/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, потому что для перебора ссылок функция скребка будет выдавать вам строки твитов одну за другой в последовательном порядке. Это означает, что при первом вызове скребкаsc, определенного какsc = scrape_coursera(), вы получите строку твита, соответствующую первой ссылке из списка ссылок, вычисленных вами в функции скребка. Если вы запустите следующий код в интерпретаторе, вы получитеstring_1 иstring_2, как показано ниже, если переменнаяlinks вscrape_coursera() содержит список, который выглядит как["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()

Теперь это генератор; он генерирует или удаляет соответствующий контент из 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, вы не сможете получать строки по одной и в последовательности. Если вы просто заменитеyield наreturn вscrape_coursera(), вы всегда получите строку, соответствующую первому сообщению в блоге, вместо того, чтобы получать первую в первом вызове, второй во втором звонке и так далее. Вы можете изменить функцию, чтобы она просто возвращалаlist всех строк, соответствующих всем ссылкам, но это требует больших затрат памяти. Кроме того, такая программа потенциально может сделать много запросов к серверам Coursera в течение короткого промежутка времени, если вы хотите быстро получить всеlist. Это может привести к тому, что вашему боту будет временно запрещен доступ к веб-сайту. Таким образом,yield лучше всего подходит для широкого круга задач по очистке, когда вам нужно, чтобы информация собиралась только по одному.

[[step-4 -—- scraping-additional-content]] == Шаг 4. Очистка дополнительного содержимого

На этом этапе вы создадите парсер дляthenewstack.io. Процесс аналогичен тому, что вы выполнили на предыдущем шаге, поэтому это будет краткий обзор.

Откройте веб-сайт в вашем браузере и проверьте источник страницы. Здесь вы увидите, что все разделы блога являются элементамиdiv классаnormalstory-box.

HTML Source Inspection of The New Stack website

Теперь вы создадите новую функцию-парсер с именемscrape_thenewstack() и сделаете запрос GET кthenewstack.io изнутри. Затем извлеките ссылки на блоги из этих элементов, а затем выполните итерации по каждой ссылке. Добавьте следующий код для достижения этой цели:

bird/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 с соответствующей ссылкой в ​​блоге. Добавьте следующий выделенный код в ваш файл для выполнения этих задач:

bird/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.

На этом этапе вы создали две функции скребка для очистки содержимого двух разных веб-сайтов, а также две вспомогательные функции для повторного использования функций, которые являются общими для двух скребков. Теперь, когда ваш бот знает, как твитнуть и что твитнуть, вы напишите код, чтобы чирикать очищенный контент.

[[step-5 -—- tweeting-the-scraped-content]] == Шаг 5. Публикация в Твиттере скопированного содержания

На этом этапе вы расширите бота, чтобы очистить содержимое двух веб-сайтов и отправить его в Твиттере через свою учетную запись Twitter. Точнее, вы хотите, чтобы он чирикнул контент с двух веб-сайтов поочередно и с регулярными интервалами в десять минут на неопределенный период времени. Таким образом, вы будете использоватьinfinite while loop для реализации желаемой функциональности. Вы сделаете это как часть функцииmain(), которая будет реализовывать основной высокоуровневый процесс, которому должен следовать ваш бот:

bird/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. Вы используете встроенную функцию Pythonglobals(). Это возвращает словарь, который отображает имена переменных на фактические переменные в вашем скрипте. Итератор - это то, что вы получаете, когда вызываете функцию скребка: например, если вы напишетеcoursera_iterator = scrape_coursera(), тогдаcoursera_iterator будет итератором, на котором вы можете вызывать вызовыnext(). Каждый вызовnext() возвращает строку, содержащую цитату и соответствующую ссылку, в точности как определено в оператореyield функцииscrape_coursera(). Каждый вызовnext() проходит через одну итерацию циклаfor в функцииscrape_coursera(). Таким образом, вы можете сделать столько вызововnext(), сколько есть ссылок на блог в функцииscrape_coursera(). Как только это число превысит, будет возбуждено исключениеStopIteration.

Как только оба итератора заполнят списокnews_iterators, запускается основной циклwhile. Внутри него у вас есть циклfor, который проходит через каждый итератор и пытается получить контент для твита. После получения контента ваш бот пишет в Твиттере, а затем спит в течение десяти минут. Если у итератора больше нет контента для предложения, возникает исключениеStopIteration, после чего вы обновляете этот итератор, повторно создавая его экземпляр, чтобы проверить наличие нового контента на исходном веб-сайте. Затем вы переходите к следующему итератору, если он доступен. В противном случае, если выполнение достигает конца списка итераторов, вы перезапускаете его с начала и отправляете в Твиттер следующий доступный контент. Это заставляет вашего бота твитить контент попеременно с двух скребков так долго, как вы захотите.

Все, что осталось, - это вызвать функциюmain(). Вы делаете это, когда интерпретатор Python вызывает скриптdirectly:

bird/bot.py

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

if __name__ == "__main__":
    main()

Ниже представлена ​​законченная версия сценарияbot.py. Вы также можете просмотретьthe script on this GitHub repository.

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

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. Это будет выглядеть примерно так:

Programmatic Tweets posted

Как вы можете видеть, бот чирикает очищенные ссылки блога со случайными цитатами из каждого блога в качестве основных моментов. Теперь этот канал представляет собой информационный канал, в котором твиты чередуются между цитатами из блогов Coursera и thenewstack.io. Вы создали бота, который собирает контент из Интернета и публикует его в Twitter. Теперь вы можете расширить сферу действия этого бота по своему желанию, добавив больше скребков для разных веб-сайтов, и бот будет чирикать контент, поступающий со всех скребков, в циклическом порядке и в нужные вам промежутки времени.

Заключение

В этом уроке вы построили базового бота в Twitter с Python и удалили некоторый контент из Интернета, чтобы ваш бот мог твитнуть. Есть много идей ботов, чтобы попробовать; Вы также можете реализовать свои собственные идеи для бота. Вы можете комбинировать универсальные функции, предоставляемые API Twitter, и создавать что-то более сложное. Чтобы получить версию более сложного бота Twitter, ознакомьтесь сchirps, фреймворком для ботов Twitter, который использует некоторые продвинутые концепции, такие как многопоточность, чтобы заставить бота выполнять несколько задач одновременно. Также есть боты с забавными идеями, напримерmisheardly. Нет никаких ограничений на креативность, которую можно использовать при создании ботов для Twitter. Очень важно найти правильные конечные точки API для реализации вашего бота.

Наконец, бот-этикет или («ботикет») важно помнить при создании вашего следующего бота. Например, если ваш бот включает ретвит, сделайте так, чтобы текст всех твитов проходил через фильтр, чтобы обнаружить ненормативную лексику, прежде чем ретвитнуть их. Вы можете реализовать такие функции, используя регулярные выражения и обработку на естественном языке. Кроме того, при поиске источников для очистки, следуйте своему мнению и избегайте источников, которые распространяют дезинформацию. Чтобы узнать больше о ботикете, вы можете посетитьthis blog post Джо Мэйо по этой теме.

Related