【BeautifulSoup】Yahoo!Newsの記事をスクレイピングして表示させる【Flask】

前書き

前回の記事でフロントエンドにてReactを適用し、バックエンドにてFlaskを動かしました。
Reactにて入力タグを取得し、タグに該当するYahoo! Newsの記事をバックエンド側で検索します。
melheaven.hatenadiary.jp

技術

  • フロントエンド側(React):入力タグの取得+記事の表示
  • バックエンド側(Flask):タグの取得+スクレイピング
  • スクレイピング→BeautifulSoup

今回はバックエンド側のスクレイピングについて詳しく説明します。

BackEnd

フロントエンド側からの入力タグ(tags)を取得し、スクレイピング関数に渡しています。

@app.route('/tags', methods=['POST'])
def parse():
    data = request.get_json()
    tags = data['post_tags']
    # スクレイピング関数
    url = scraping(tags)
    response = {'result': url}
    return make_response(jsonify(response))

スクレイピング

今回はBeautifulSoupを用います。
BeautifulSoupとは、XMLとHTMLで構成されたファイルを解析するためのPythonライブラリです。
近年は自然言語処理の需要と同時に、スクレイピング技術の重要性も高まってきています。

pip install BeautifulSoup4

URLにアクセスしてリソースを取得するためのPythonパッケージであるUrllibをインポートします。

from bs4 import BeautifulSoup
import urllib

日本語を含んだURLをurllibでアクセスしようとすると、ASCII変換できない文字がURLのクエリに含まれている場合、先にURLの日本語部分をパースしてエンコードします。

qiita.com

def encode(url):
    p = urlparse(url)
    query = urllib.parse.quote_plus(p.query, safe='=&')
    url = '{}://{}{}{}{}{}{}{}{}'.format(
    p.scheme, p.netloc, p.path,
    ';' if p.params else '', p.params,
    '?' if p.query else '', query,
    '#' if p.fragment else '', p.fragment)
    return url

今回はタグ(単語)を検索して、関連する記事を取得する機能としました。
例えば「野球」「パリーグ」のタグで検索したい場合のURL+クエリは以下の通りです。

"https://news.yahoo.co.jp/search?p=野球%20パリーグ&ei=utf-8&aq=0"

https://news.yahoo.co.jp/search?p=%E9%87%8E%E7%90%83+%E3%83%91%E3%83%AA%E3%83%BC%E3%82%B0&ei=utf-8

%20は" "(スペース)のASCII文字となりますので、後にASCII変換を行ってくれることを考慮して、以下のようにURL+クエリを生成します。

def scraping(tags):
    tagURL = " ".join(tags)
    url = "https://news.yahoo.co.jp/search?p=%s&ei=utf-8&aq=0"%tagURL
    url = encode(url)
    response = urllib.request.urlopen(url)
    content = response.read().decode(response.headers.get_content_charset())
    # 以下に続く

BeautifulSoupを使って、記事のタイトルとリンク情報を取得します。
Yahoo! Newsの検索一覧ページの場合、タイトル情報はdivタグの内部のclass="newsFeed_item_title"のtext部分に記述されています。
そこでBeautifulSoupのfind_allによって該当部分のHTMLタグを全て取得します。
最後にリンクでも同様に実装し、記事のタイトル・リンク情報をまとめてフロントエンドに送信します。

    soup = BeautifulSoup(content, 'html.parser')

    # タイトル情報取得
    titles = soup.find_all('div', class_={'newsFeed_item_title'})
    titles = list(map(lambda x: x.text, titles))

    # リンク取得
    # https://naruport.com/blog/2019/12/27/bs4-href/
    links = soup.find_all('a', class_={'newsFeed_item_link'})
    links = list(map(lambda x: x.get('href'), links))

    articles = []
    for title, link in zip(titles, links):
        articles.append({'title': title, 'link': link})
    

結果

「競馬」「有馬記念」と検索したのち、ヒットするYahoo! Newsの記事を列挙してくれます。

f:id:electric-city:20210101002346p:plain
抽出した記事の列挙