Python 3でWebページをスクレイピングしてTwitterにコンテンツを投稿する方法

著者は、Write for DOnationsプログラムの一部として寄付を受け取るためにThe Computer History Museumを選択しました。

前書き

Twitterボットは、ソーシャルメディアを管理するだけでなく、マイクロブログネットワークから情報を抽出する強力な方法です。 Twitterの汎用APIを活用することで、ボットは、ツイート、リツイート、「お気に入りツイート」、特定の関心を持つ人々のフォロー、自動返信など、多くのことを実行できます。 人々はボットの力を悪用し、他のユーザーにマイナスの体験をもたらす可能性がありますが、調査によれば、人々はTwitterボットを信頼できる情報源と見なしています。 たとえば、ボットは、あなたがオンラインでないときでもフォロワーをコンテンツに引き付け続けます。 一部のボットは、@EarthquakesSFなどの重要で役立つ情報を提供します。 ボットのアプリケーションは無限です。 2019年の時点で、ボットはTwitterで約24% of all tweetsを占めると推定されています。

このチュートリアルでは、Python用のthis Twitter API libraryを使用してTwitterボットを構築します。 TwitterアカウントのAPIキーを使用して、ボットを認証し、2つのWebサイトからコンテンツをスクレイピングできるを構築します。 さらに、これら2つのWebサイトのコンテンツを、設定された時間間隔で交互にツイートするようにボットをプログラムします。 このチュートリアルではPython 3を使用することに注意してください。

前提条件

このチュートリアルを完了するには、次のものが必要です。

[.note]#Note: Twitterで開発者アカウントを設定します。これには、このボットに必要なAPIキーにアクセスする前にTwitterによるアプリケーションのレビューが含まれます。 ステップ1では、アプリケーションを完了するための具体的な詳細について説明します。

[[step-1 -—- setting-up-your-developer-account-and-accessing-your-twitter-api-keys]] ==ステップ1—開発者アカウントの設定とTwitterAPIキーへのアクセス

ボットのコーディングを開始する前に、ボットのリクエストを認識するためにTwitterのAPIキーが必要になります。 この手順では、Twitter開発者アカウントを設定し、TwitterボットのAPIキーにアクセスします。

APIキーを取得するには、developer.twitter.comに移動し、ページの右上のセクションにあるApplyをクリックして、ボットアプリケーションをTwitterに登録します。

次に、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 NameCountryを選択したら、次のセクションに進みます。 What use case(s) are you interested in?の場合は、Publish and curate TweetsおよびStudent project / Learning to codeオプションを選択します。 これらのカテゴリは、このチュートリアルを完了する理由を最もよく表しています。

Twitter Bot Purpose

次に、構築しようとしているボットの説明を入力します。 Twitterは、ボットの悪用から保護するためにこれを必要とします。 2018年に彼らはそのような審査を導入しました。 このチュートリアルでは、The New StackThe Coursera Blogから技術に焦点を当てたコンテンツをスクレイピングします。

descriptionボックスに何を入力するかを決定するときは、このチュートリアルの目的のために、次の行で回答をモデル化します。

チュートリアルに従って、thenewstack.io(The New Stack)やblog.coursera.org(Coursera’s Blog)などのWebサイトからコンテンツを取得し、それらから引用をツイートするTwitterボットを作成しています。 スクレイピングされたコンテンツは集約され、Pythonジェネレーター関数を介してラウンドロビン方式でツイートされます。

最後に、Will your product, service, or analysis make Twitter content or derived information available to a government entity?noを選択します

Twitter Bot Intent

次に、Twitterの利用規約に同意し、Submit applicationをクリックして、メールアドレスを確認します。 このフォームを送信すると、Twitterから確認メールが送信されます。

メールアドレスを確認すると、申し込みプロセスのフィードバックフォームが記載されたApplication under reviewページが表示されます。

また、レビューに関してTwitterから別のメールを受け取ります。

Application Review Email

Twitterのアプリケーションレビュープロセスのタイムラインは大きく異なる場合がありますが、多くの場合、Twitterは数分以内にこれを確認します。 ただし、アプリケーションのレビューにこれより時間がかかる場合は珍しくありません。1〜2日以内にレビューを受け取る必要があります。 確認を受け取ると、Twitterはキーの生成を許可します。 developer.twitter.com/appsでアプリの詳細ボタンをクリックすると、[Keys and tokens]タブでこれらにアクセスできます。

最後に、アプリのページの[Permissions]タブに移動し、ツイートコンテンツも書き込みたいので、Access PermissionオプションをRead and Writeに設定します。 通常、傾向の分析、データマイニングなどの研究目的で読み取り専用モードを使用します。 最後のオプションでは、チャットボットはダイレクトメッセージへのアクセスを必要とするため、ユーザーはチャットボットを既存のアプリに統合できます。

Twitter App Permissions Page

Twitterの強力なAPIにアクセスできます。これはボットアプリケーションの重要な部分です。 次に、環境をセットアップして、ボットの構築を開始します。

[[step-2 -—- building-the-essentials]] ==ステップ2—Essentialsの構築

このステップでは、APIキーを使用してTwitterでボットを認証するコードを記述し、Twitterハンドルを介してプログラムによる最初のツイートを行います。 これは、The New StackCoursera Blogからコンテンツを取得し、定期的にツイートするTwitterボットを構築するという目標に向けた道のりの良いマイルストーンとして役立ちます。

最初に、プロジェクトフォルダーとプロジェクトの特定のプログラミング環境を設定します。

プロジェクトフォルダーを作成します。

mkdir bird

プロジェクトフォルダーに移動します。

cd bird

次に、プロジェクト用の新しいPython仮想環境を作成します。

python3 -m venv bird-env

次に、次のコマンドを使用して環境をアクティブ化します。

source bird-env/bin/activate

これにより、ターミナルウィンドウのプロンプトに(bird-env)プレフィックスが付加されます。

次に、テキストエディタに移動して、credentials.pyというファイルを作成します。このファイルには、TwitterAPIキーが保存されます。

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'

次に、Twitterにリクエストを送信するためのメインAPIライブラリをインストールします。 このプロジェクトでは、次のライブラリが必要です:nltkrequeststwitterlxmlrandom、およびtimerandomtimeはPythonの標準ライブラリの一部であるため、これらのライブラリを個別にインストールする必要はありません。 残りのライブラリをインストールするには、Pythonのパッケージマネージャーであるpipを使用します。

ターミナルを開き、プロジェクトフォルダーにいることを確認して、次のコマンドを実行します。

pip3 install lxml nltk requests twitter
  • lxmlおよびrequests:Webスクレイピングに使用します。

  • twitter:これはTwitterのサーバーへのAPI呼び出しを行うためのライブラリです。

  • nltk :(自然言語ツールキット)ブログの段落を文に分割するために使用します。

  • random:これを使用して、スクレイピングされたブログ投稿全体の一部をランダムに選択します。

  • time:特定のアクションの後にボットを定期的にスリープさせるために使用します。

ライブラリをインストールしたら、プログラミングを開始する準備が整いました。 次に、資格情報をボットを実行するメインスクリプトにインポートします。 credentials.pyと一緒に、テキストエディタからbirdプロジェクトディレクトリにファイルを作成し、bot.pyという名前を付けます。

nano bot.py

実際には、ボットがますます高度になるにつれて、ボットの機能を複数のファイルに分散します。 ただし、このチュートリアルでは、デモンストレーションの目的で、すべてのコードを1つのスクリプト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関数を使用して、スクレイプされたWebページの文字列ソースをツリー構造に変換し、ページから関連情報を簡単に抽出できるようにします。 OAuthは、キーから認証オブジェクトを構築するのに役立ち、Twitterは、Twitterのサーバーとの今後のすべての通信のためのメインAPIオブジェクトを構築します。

次に、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_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!

エラーが発生した場合は、保存した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と入力して、インタープリターを閉じます。

これで、ボットにツイートするための基本的な機能が追加されました。 有用なコンテンツをツイートするボットを開発するには、次のステップでWebスクレイピングを組み込みます。

[[step-3 -—- scraping-websites-for-your-tweet-content]] ==ステップ3—ツイートコンテンツのWebサイトをスクレイピングする

より興味深いコンテンツをタイムラインに導入するには、the New StackCoursera Blogからコンテンツを取得し、このコンテンツをツイートの形式でTwitterに投稿します。 通常、ターゲットWebサイトから適切なデータを取得するには、HTML構造を実験する必要があります。 このチュートリアルで作成するボットから送信される各ツイートには、選択したWebサイトからのブログ投稿へのリンクと、そのブログからのランダムな引用があります。 この手順は、Courseraからコンテンツをスクレイピングするための固有の関数内に実装するため、scrape_coursera()という名前を付けます。

最初に開いたbot.py

nano bot.py

ファイルの最後にscrape_coursera()関数を追加します。

bird/bot.py

...
t = Twitter(auth=oauth)


def scrape_coursera():

ブログから情報を取得するには、まずCourseraのサーバーに関連するウェブページをリクエストします。 そのためには、requestsライブラリのget()関数を使用します。 get()はURLを受け取り、対応するWebページをフェッチします。 したがって、blog.coursera.orgを引数としてget()に渡します。 ただし、GETリクエストでヘッダーを提供する必要もあります。これにより、Courserのサーバーが本物のクライアントとして認識されるようになります。 次の強調表示された行を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'
        }

このヘッダーには、特定のオペレーティングシステムで実行されている定義済みのWebブラウザーに関する情報が含まれます。 この情報(通常はUser-Agentと呼ばれます)が実際のWebブラウザーとオペレーティングシステムに対応している限り、ヘッダー情報が実際のWebブラウザーとコンピューター上のオペレーティングシステムと一致しているかどうかは関係ありません。 したがって、このヘッダーはすべてのシステムで正常に機能します。

ヘッダーを定義したら、次の強調表示された行を追加して、ブログWebページのURLを指定してCourseraにGETリクエストを行います。

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)

これにより、Webページがマシンにフェッチされ、Webページ全体の情報が変数rに保存されます。 rcontent属性を使用して、WebページのHTMLソースコードを評価できます。 したがって、r.contentの値は、ページを右クリックしてInspect Elementオプションを選択することにより、ブラウザーでWebページを検査したときに表示される値と同じです。

ここでは、fromstring関数も追加しました。 ウェブページのソースコードをlxmlライブラリからインポートされたfromstring関数に渡して、ウェブページのtree構造を構築できます。 このtree構造により、Webページのさまざまな部分に簡単にアクセスできます。 HTMLソースコードは特定のツリーのような構造を持っています。すべての要素は<html>タグで囲まれ、その後ネストされます。

次に、ブラウザでhttps://blog.coursera.orgを開き、ブラウザの開発ツールを使用してそのHTMLソースを調べます。 ページを右クリックして、Inspect Elementオプションを選択します。 ブラウザの下部にウィンドウが表示され、ページのHTMLソースコードの一部が表示されます。

browser-inspect

次に、表示されているブログ投稿のサムネイルを右クリックして調べます。 HTMLソースは、ブログのサムネイルが定義されている関連するHTML行を強調表示します。 このページのすべてのブログ投稿は、class"recent"<div>タグ内で定義されていることに気付くでしょう。

blog-div

したがって、コードでは、このようなブログ投稿のdiv要素をすべてXPath経由で使用します。これは、Webページの要素に対処する便利な方法です。

これを行うには、次のように関数を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()

ここで、XPathtree.xpath()に渡される文字列)は、class"recent"のWebページソース全体からdiv要素が必要であることを伝えます。 //はウェブページ全体の検索に対応し、divは関数にdiv要素のみを抽出するように指示し、[@class="recent"]はそれらのdiv要素のみを抽出するように要求します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スクリプトから最後の2つの強調表示された行を削除できます。

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とともにこの文を引用としてツイートします。 ランダムな文の抽出には、次の3つの部分が含まれます。

  1. ブログ投稿のすべての段落をリストとして取得します。

  2. 段落のリストから段落をランダムに選択します。

  3. この段落からランダムに文を選択します。

ブログの投稿ごとにこれらの手順を実行します。 取得するには、リンクのGETリクエストを作成します。

ブログのコンテンツにアクセスできるようになったので、次の3つのステップを実行して必要なコンテンツを抽出するコードを紹介します。 3つのステップを実行するスクレイピング関数に次の拡張子を追加します。

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

最初のリンクを開いてブログ投稿を調べると、すべての段落が、クラスとしてentry-contentを持つdivタグに属していることがわかります。 したがって、すべての段落をparas = blog_tree.xpath('//div[@class="entry-content"]/p')のリストとして抽出します。

Div Enclosing Paragraphs

リスト要素はliteralの段落ではありません。それらはElementobjectsです。 これらのobjectsからテキストを抽出するには、text_content()メソッドを使用します。 この行は、Pythonのlist comprehensionデザインパターンに従います。このパターンは、通常1行で書き出されるループを使用してコレクションを定義します。 bot.pyでは、各段落要素objectのテキストを抽出し、テキストが空でない場合はlistに格納します。 この段落リストからランダムに段落を選択するには、randomモジュールを組み込みます。

最後に、変数paraに格納されているこの段落からランダムに文を選択する必要があります。 このタスクでは、最初に段落を文に分割します。 これを実現するための1つのアプローチは、Pythonのsplit()メソッドを使用することです。 ただし、文は複数のブレークポイントで分割される可能性があるため、これは難しい場合があります。 したがって、分割タスクを簡素化するために、nltkライブラリを介した自然言語処理を活用します。 チュートリアルの前半で定義したtokenizerオブジェクトは、この目的に役立ちます。

文のリストができたので、random.choice()を呼び出してランダムな文を抽出します。 この文をツイートの引用にしたいので、280文字を超えることはできません。 ただし、審美的な理由から、大きすぎたり小さすぎたりしない文を選択します。 ツイート文の長さは60〜210文字にする必要があることを指定します。 random.choice()が選択する文は、この基準を満たさない可能性があります。 適切な文を識別するために、スクリプトは10回試行し、毎回基準をチェックします。 ランダムに選択された文が基準を満たすと、ループから抜け出すことができます。

確率は非常に低くなりますが、10回の試行内でこのサイズの条件を満たしている文はない可能性があります。 この場合、対応するブログ投稿を無視して、次のブログ投稿に進みます。

引用する文ができたので、対応するリンクでツイートできます。 これを行うには、ランダムにピックアップされた文と対応するブログリンクを含む文字列を生成します。 この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)

スクリプトは、先行するforループが中断しない場合にのみ、elseステートメントを実行します。 したがって、ループがサイズ条件に適合する文を見つけることができない場合にのみ発生します。 その場合、この関数を呼び出すコードがツイートするものが何もないと判断できるように、単にNoneを生成します。 次に、関数を再度呼び出して、次のブログリンクのコンテンツを取得します。 しかし、ループが壊れた場合は、関数が適切な文を見つけたことを意味します。スクリプトはelseステートメントを実行せず、関数は単一のwhitespaceで区切られた文とブログリンクで構成される文字列を生成します。

scrape_coursera()関数の実装はほぼ完了しています。 別のWebサイトをスクレイピングするために同様の機能を作成する場合は、Courserのブログをスクレイピングするために記述したコードの一部を繰り返す必要があります。 コードの一部の書き換えや複製を回避し、ボットのスクリプトがDRYの原則(Do n't Repeat Yourself)に従っていることを確認するために、コードの一部を特定して抽象化し、後で記述されるスクレーパー関数。

関数がスクレイピングしているWebサイトに関係なく、段落をランダムに選択してから、この選択した段落からランダムな文を選択する必要があります。これらの機能を別の関数で抽出できます。 次に、スクレーパー関数からこれらの関数を呼び出すだけで、目的の結果を得ることができます。 scrape_coursera()関数の外側にHEADERSを定義して、すべてのスクレーパー関数がそれを使用できるようにすることもできます。 したがって、次のコードでは、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という名前を付けることができます。 10回試行してもそのような文が検出されない場合、関数は代わりに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を保存して終了します。

ここでは、returnの代わりにyieldを使用しています。これは、リンクを反復処理するために、スクレーパー関数がツイート文字列を1つずつ順番に提供するためです。 つまり、sc = scrape_coursera()として定義されたスクレーパーscを最初に呼び出すと、スクレーパー関数内で計算したリンクのリストの最初のリンクに対応するツイート文字列が取得されます。 インタプリタで次のコードを実行すると、scrape_coursera()内のlinks変数が%のようなリストを保持している場合、以下に示すようにstring_1string_2を取得します。 (t8)s。

python3 -i bot.py

スクレーパーをインスタンス化し、scと呼びます。

>>> sc = scrape_coursera()

現在はジェネレーターです。 Courseraから関連コンテンツを1つずつ生成またはスクレイピングします。 scを介してnext()を順番に呼び出すことにより、スクレイピングされたコンテンツに1つずつアクセスできます。

>>> 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を使用すると、文字列を1つずつ順番に取得することはできません。 yieldscrape_coursera()returnに置き換えるだけの場合、最初の呼び出しで最初の文字列を取得するのではなく、最初のブログ投稿に対応する文字列を常に取得します。 2回目の呼び出しで、というように続きます。 関数を変更して、すべてのリンクに対応するすべての文字列のlistを単純に返すことができますが、これはメモリを大量に消費します。 また、この種のプログラムでは、list全体をすばやく処理したい場合、Courseraのサーバーに短時間で多くのリクエストを送信する可能性があります。 これにより、ボットが一時的にWebサイトへのアクセスを禁止される可能性があります。 したがって、yieldは、一度に1つずつスクレイピングする情報のみが必要なさまざまなスクレイピングジョブに最適です。

[[step-4 -—- scraping-additional-content]] ==ステップ4—追加コンテンツのスクレイピング

このステップでは、thenewstack.ioのスクレーパーを作成します。 このプロセスは前のステップで完了したものと似ているため、簡単な概要になります。

ブラウザでWebサイトを開き、ページのソースを調べます。 ここでは、すべてのブログセクションがクラスnormalstory-boxdiv要素であることがわかります。

HTML Source Inspection of The New Stack website

次に、scrape_thenewstack()という名前の新しいスクレーパー関数を作成し、その中からthenewstack.ioに対してGETリクエストを作成します。 次に、これらの要素からブログへのリンクを抽出し、各リンクを繰り返します。 これを実現するには、次のコードを追加します。

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フラグを使用するのは、Webサイトでセキュリティ証明書の有効期限が切れている場合があり、ここでの場合のように、機密データが含まれていない場合はそれらにアクセスできます。 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属性を使用して画像のリンクにアクセスできます。

この時点で、2つの異なるWebサイトからコンテンツをスクレイピングするための2つのスクレーパー関数を構築し、2つのスクレーパーに共通の機能を再利用するための2つのヘルパー関数も構築しました。 ボットがツイートする方法とツイートする方法を知ったので、スクレイピングされたコンテンツをツイートするコードを作成します。

[[step-5 --- tweeting-the-scraped-content]] ==ステップ5—スクレイピングされたコンテンツをツイートする

このステップでは、ボットを拡張して2つのWebサイトからコンテンツを取得し、Twitterアカウント経由でツイートします。 より正確には、2つのWebサイトのコンテンツを無期限に10分間隔で交互にツイートする必要があります。 したがって、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リストに追加してデータを入力します。 Pythonの組み込みのglobals()関数を使用しています。 これは、変数名をスクリプト内の実際の変数にマップする辞書を返します。 イテレータは、スクレーパー関数を呼び出すときに取得するものです。たとえば、coursera_iterator = scrape_coursera()を書き込むと、coursera_iteratornext()呼び出しを呼び出すことができるイテレータになります。 各next()呼び出しは、scrape_coursera()関数のyieldステートメントで定義されているとおりに、引用符とそれに対応するリンクを含む文字列を返します。 各next()呼び出しは、scrape_coursera()関数のforループの1回の反復を通過します。 したがって、scrape_coursera()関数にブログリンクがある数だけnext()呼び出しを行うことができます。 その数を超えると、StopIteration例外が発生します。

両方のイテレータがnews_iteratorsリストに入力されると、メインのwhileループが開始されます。 その中には、各イテレータを通過し、ツイートするコンテンツを取得しようとするforループがあります。 コンテンツを取得した後、ボットはそれをツイートし、10分間スリープします。 イテレータに提供するコンテンツがこれ以上ない場合は、StopIteration例外が発生し、イテレータを再インスタンス化して更新し、ソースWebサイトで新しいコンテンツが利用可能かどうかを確認します。 次に、可能な場合は次のイテレータに進みます。 それ以外の場合、実行がイテレータリストの最後に達すると、最初からやり直して、次に利用可能なコンテンツをツイートします。 これにより、ボットが2つのスクレイパーから交互にコンテンツをツイートできるようになります。

現在残っているのは、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

ご覧のとおり、ボットは、スクレイプされたブログのリンクを、各ブログからのランダムな引用をハイライトとしてツイートしています。 このフィードは、Courraとthenewstack.ioからのブログの引用を交互にツイートする情報フィードになりました。 Webからコンテンツを集約してTwitterに投稿するボットを構築しました。 さまざまなWebサイトにスクレーパーを追加することで、このボットの範囲を希望どおりに広げることができます。ボットは、すべてのスクレーパーからのコンテンツをラウンドロビン方式で、希望する時間間隔でツイートします。

結論

このチュートリアルでは、Pythonを使用して基本的なTwitterボットを構築し、ボットがツイートするためにWebから一部のコンテンツをスクレイピングしました。 試すべきボットのアイデアはたくさんあります。ボットのユーティリティに関する独自のアイデアを実装することもできます。 TwitterのAPIが提供する多機能な機能を組み合わせて、より複雑なものを作成できます。 より洗練されたTwitterボットのバージョンについては、chirpsを確認してください。これは、マルチスレッドなどの高度な概念を使用してボットに複数のことを同時に実行させるTwitterボットフレームワークです。 misheardlyのような楽しいアイデアボットもいくつかあります。 Twitterボットを作成するときに使用できる創造性に制限はありません。 ボットの実装に適した適切なAPIエンドポイントを見つけることが不可欠です。

最後に、ボットのエチケットまたは(「ボチケット」)は、次のボットを構築するときに留意することが重要です。 たとえば、ボットにリツイートが組み込まれている場合、ツイートのすべてのテキストをフィルターに通して、不正な言語を検出してからリツイートするようにします。 正規表現と自然言語処理を使用して、このような機能を実装できます。 また、スクレイピングするソースを探している間、あなたの判断に従って、誤った情報を広めるものを避けてください。 ボティケットの詳細については、このトピックに関するJoe Mayoのthis blog postにアクセスしてください。