Веб-скрапинг страниц и создание твитов с помощью Python 3

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

В предыдущем мануале мы подготовили среду для разработки вашего Twitter бота и создали базовый код для скрапинга веб-страниц Coursera. В этой части вы узнаете, как настроить скрапинг дополнительного контента.

Примечание: Этот мануал является продолжением мануала Веб-скрапинг страниц и создание твитов с помощью Python 3: настройка базовой среды. Эти части тесно связаны и должны выполняться в одной среде. Потому предполагается, что вы успешно выполнили первую часть этой серии.

1: Скрапинг дополнительного контента

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

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

Теперь нужно создать новую функцию скрапинга по имени 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 отключает проверку сертификатов и продолжает извлекать данные, как обычно. В противном случае метод выдает ошибку об истекших сертификатах безопасности.

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

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

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

2: Публикация собранного контента в 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. После этого бот создает пустой список, который будет содержать фактические функции скрапинга; этот список будет называться news_iterators. Бот заполняет его, просматривая каждое имя в списке news_funcs и добавляя соответствующий итератор в список news_iterators. Бот использует встроенную в Python функцию globals(). Она возвращает словарь, который связывает имена переменных и фактические переменные в вашем скрипте. Итератор – это то, что вы получаете, когда вызываете функцию скрапинга: например, если вы напишите 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:

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

Ниже приведена финальная версия скрипта bot.py. Вы также можете просмотреть скрипт в этом репозитории GitHub.

"""Main bot script - 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
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:

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/

После пробного запуска вашего бота вы увидите полную хронологию программируемых твитов, размещенных вашим ботом на вашей странице в Twitter.

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

Заключение

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

Наконец, при создании бота важно помнить о бот-этикете (или botiquette). Например, если ваш бот встраивает ретвиты, настройте предварительную фильтрацию текста твитов, чтобы обнаружить ненормативную лексику. Для этого можно использовать регулярные выражения и обработку естественного языка. Кроме того, при скрапинге следует избегать источников, которые распространяют дезинформацию. Чтобы узнать больше о бот-этикете, читайте этот пост.

Tags: ,