質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.38%

beautifulsoupでMediumの本文を抽出したい

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 85

こんにちは。Pythonでスクレイピングを行っています。
例えば
https://medium.com/cotinetwork/coti-newsletter-september-20th-a9cac08e22df
こちらの記事で「本文だけ」を抽出したいのです。

htmlを見ると、

<p id="8149" class="hy hz ct ia b ib ic id ie if ig ih ii ij ik il im in io ip iq ir is it iu iv cl dq" data-selectable-paragraph="">Following the <a class="el iw" rel="noopener" href="/cotinetwork/beyond-payments-cotis-growth-plan-to-become-a-next-gen-financial-ecosystem-2907949df002">announcement</a> of COTI’s growth plan, various media outlets have provided coverage on COTI’s roadmap to become a next-generation financial ecosystem. COTI was recently featured on <a href="https://www.crypto-news-flash.com/coti-update-explains-native-tokens-recent-rally/" class="el iw" target="_blank" rel="noopener ugc nofollow">Crypto New Flash</a>, <a href="https://coinquora.com/coti-outlines-plans-for-the-future-as-native-token-soars/" class="el iw" target="_blank" rel="noopener ugc nofollow">CoinQuora</a>, and <a href="https://u.today/coti-releases-growth-plan-treasury-stablecoin-factory-and-ecosystem" class="el iw" target="_blank" rel="noopener ugc nofollow">U.TODAY.</a></p>
```
…(以下略)

と書かれているので、本文のクラス名が
hy hz ct ia b ib ic id ie if ig ih ii ij ik il im in io ip iq ir is it iu iv cl dq
なのは分かるのですが、これをbeautiful soupを用いて取得することはできますでしょうか?

【試してみたこと】
①contents = soup.find_all('p', class_="hy hz ct ia b ib ic id ie if ig ih ii ij ik il im in io ip iq ir is it iu iv cl dq")
で本文の取得自体はできます。しかし、このクラス名自体を自動でpythonで取得できるようにしたいのです。
②a = soup.select("body > div > div > div:nth-child(3) > article > div > div > section > div:nth-child(3) > div > p")
などのCSS参照も考えてみたのですが、ブログごと/記事ごとに階層構造が異なるため、うまい参照方法が思いつきません。
③feedparserも試してみました。
feeds.entries[0].content
により、本文をタグ付きで取得することができましたが、<figure><li><img>タグなど余計な要素が入るため、これは消したいです。したがって、beautifulsoupの方が効率的かなと思っています。

どうぞお知恵をお貸しください。beautifulsoup以外のライブラリを用いても構いません。

宜しくお願いします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

0

最初に soup.select_one() で p タグ要素を一つ取得して class attribute の値を取得しておきます。そして、その値を使って本文を抽出します。

import requests
from bs4 import BeautifulSoup

url = 'https://medium.com/cotinetwork/coti-newsletter-september-20th-a9cac08e22df'
r = requests.get(url)
soup = BeautifulSoup(r.content , 'html.parser')

# p tag element which has id and class attributes
cls_name = ' '.join(soup.select_one('p[id][class]').get('class'))
text = '\n'.join(i.text for i in soup.select(f'p[class="{cls_name}"]'))

print(text)

# 適宜改行を入れています
Following the announcement of COTI’s growth plan, various media outlets have provided coverage
on COTI’s roadmap to become a next-generation financial ecosystem. COTI was recently featured on
Crypto New Flash, CoinQuora, and U.TODAY.

※ どうやら class attribute の値が p タグごとに微妙に異なっているらしく、最初の paragraph しか取れていません。

class="hy hz ct ia b ib ic id ie if ig ih ii ij ik il im in io ip iq ir is it iu iv cl dq"

最初の 8 文字(hy hz ct)で照合すると 4626 文字抽出されて本文全体が取れている様な感じです。

text = '\n'.join(i.text for i in soup.select(f'p[class*="{cls_name[:8]}"]'))

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2021/11/23 21:34

    すごい!できました、ありがとうございます。
    ただ、こちらは本文全体ではないようです。
    理由は、1つめの段落と2つ目以降の段落が微妙にclass名が異なっていました。
    本文全体を取得できますかね?

    キャンセル

  • 2021/11/23 21:44

    はい、
    text = '\n'.join(i.text for i in soup.select(f'p[class="{cls_name}"]'))
    を以下に変えてみて下さい。
    text = '\n'.join(i.text for i in soup.select(f'p[class*="{cls_name[:8]}"]'))

    これは、class 属性に "hy hz ct" が含まれている p タグという意味です(どうやら後ろの方が違った文字列になっている様です)。

    キャンセル

  • 2021/11/23 22:27

    丁寧に教えていただき本当にありがとうございます!!
    また疑問点がわきましたら質問させていただきます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.38%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る