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

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

新規登録して質問してみよう
ただいま回答率
85.35%
スクレイピング

スクレイピングとは、公開されているWebサイトからページ内の情報を抽出する技術です。

CSV

CSV(Comma-Separated Values)はコンマで区切られた明白なテキスト値のリストです。もしくは、そのフォーマットでひとつ以上のリストを含むファイルを指します。

Beautiful Soup

Beautiful Soupは、Pythonのライブラリの一つ。スクレイピングに特化しています。HTMLデータの構文の解析を行うために、HTMLタグ/CSSのセレクタで抽出する部分を指定することが可能です。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

2回答

2076閲覧

python for文のリストの変数の値の中の文字列から a タグの値を抽出したい。

dub

総合スコア24

スクレイピング

スクレイピングとは、公開されているWebサイトからページ内の情報を抽出する技術です。

CSV

CSV(Comma-Separated Values)はコンマで区切られた明白なテキスト値のリストです。もしくは、そのフォーマットでひとつ以上のリストを含むファイルを指します。

Beautiful Soup

Beautiful Soupは、Pythonのライブラリの一つ。スクレイピングに特化しています。HTMLデータの構文の解析を行うために、HTMLタグ/CSSのセレクタで抽出する部分を指定することが可能です。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2021/08/07 07:42

やりたい事

見出し(h3タグ)・本文・画像へのリンク の3つがセットになり、それが複数セットあるページをスクレイピングして、それぞれ3つの要素を抜き出してCSV出力したいと考えています。

H3見出し以外 の本文、画像へのリンクは すべて Pタグなのでスクレイピングする要素が少なく困っていたところ、下記サンプルコードを公開頂いているページを見て、このコードを改良したらいけそう・・と思いました。

改良前サンプルコード

HTML から本文のテキストだけを抽出する Python コード例(見出しタグと見出しに属するテキストを取得)

上記サンプルコードでは、見出しと見出しに属するテキスト の2つセットをCSVにて出力しているのですが、

私の場合は、「見出し、見出しに属するテキスト、見出しに属する画像へのリンク」の3つをセットにして取得したいと考えました

取得するHTML
見出し
見出しに属する本文 の最後に
本文+画像リンクとなっている

<p>本文(不要な文字列)+ 画像へのリンクページ <a href='URL’ >画像URL</a> </p> ```取得HTML  <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>タイトル</title> </head> <body> <div> <h3>あああ</h3> <p>本文テキスト1。</p> <p>本文テキスト2。</p> <p>本文テキスト3。<a href='https://******.com/***.html>https://*****.com/***.html</a></p> ※画像ページへのリンクだが、.htmlとページに画像が埋め込みしてある。 hrefも画像と同じURLが記載ある
<h3>いいい</h3> <p>本文テキスト4。</p> <p>本文テキスト5。<a href='https://******.com/***.html>https://*****.com/***.html</a></p> <h3>ううう</h3> <p>本文テキスト6。</p>

  <p>本文テキスト6。</p>
<p>本文テキスト7 <a href='https://.com/.html>https://.com/*.html</a></p>
</p>

</div>
</body> </html> ```

下記のように自分で改良してみたのですが・・・

改良した部分には ■改良部分としています

python

1"""extract_text.py""" 2 3import re 4import csv 5from pathlib import Path 6from lxml import html 7 8 9# 不要なタグを検索する xpath 表現のタプル 10REMOVE_TAGS = ('.//style', './/script', './/noscript') 11 12# 見出しタグを検索する xpath 表現 13XPATH_H_TAGS = './/h1|.//h2|.//h3|.//h4|.//h5|.//h6' 14 15# 見出しタグを検出するための正規表現 16RE_H_MATCH = re.compile('^h[1-6]$').match 17 18 19def main(): 20 """メイン関数""" 21 22 # (1/8) HTML ファイルを指定します 23 src_file = Path(r'***\source.html') 24 25 # (2/8) HTML データを取得します 26 with src_file.open('rb') as f: 27 html_data = f.read() 28 29 # (3/8) HTML を解析します 30 root = html.fromstring(html_data) 31 32 # (4/8) HTML から不要なタグを削除します 33 for remove_tag in REMOVE_TAGS: 34 for tag in root.findall(remove_tag): 35 tag.drop_tree() 36 37 # (5/8) テキストの入れ物を用意します 38 # (デバッグ用にラベル行も追加) 39 texts = [] 40 texts.append( 41 ['タグ名', 'タグテキスト', 'タグに属するテキスト','タグに属する画像URL#■改良部分■']) 42 43 # (6/8) タイトルタグを取得します 44 t = root.find('.//title') 45 if t is not None: 46 text = t.text_content() 47 48 # 空でなければリストに追加 49 if text: 50 texts.append([t.tag, text, '']) 51 52 print(f'(デバッグ) {t.tag}: {text}\n') 53 54 # (7/8) 見出しタグを検索します 55 for h_tag in root.xpath(XPATH_H_TAGS): 56 # 見出しタグのテキストを取得 57 h_text = h_tag.text_content() 58 59 print(f'(デバッグ) {h_tag.tag}: {h_text}') 60 61 # 見出しタグと同じ階層にあったテキストを入れるリスト 62 contents = [] 63 #■改良部分■ リンクの変数定義 64     elem_link=[] 65 66 # 見出しの次のタグを取得 67 next_tag = h_tag.getnext() 68 69 # 次のタグがなくなるまでループ 70 while next_tag is not None: 71 # タグが見出しだったらブレーク 72 if RE_H_MATCH(next_tag.tag): 73 print(f'(デバッグ) 次の見出しタグ {next_tag.tag} が見つかった。') 74 print(f'(デバッグ) while ブレーク\n') 75 break 76 77 # タグのテキストを取得 78 text = next_tag.text_content() 79 80 # 空でなければリストに追加←#■改良部分■ リンクが存在しない行のみtextに追加 81 if 'http' not in text: 82 contents.append(text) 83 84 print(f'(デバッグ) {next_tag.tag}: {text}') 85 86 ##■改良部分■ httpがあればリンクの変数elem_linkに追加  87 if 'http' in text: 88 elem_link.append(text) 89 90 # さらに次のタグを取得してループする 91 next_tag = next_tag.getnext() 92 else: 93 # 同じ階層のタグをたどり尽くして、次のタグが無かった場合。 94 print(f'(デバッグ) 次のタグが無かった。 {next_tag}') 95 96 # リストを連結してひとつの文字列にします 97 contents = '|'.join(contents) 98 99 # リストに追加 #■改良部分■ 4列目にリンクの値のみを表示したいが 本文とセットになった行が表示される 100 texts.append([h_tag.tag, h_text, contents,elem_link]) 101 102 # (8/8) テキストを CSV に保存します 103 csv_file = Path(r'***\texts.csv') 104 with csv_file.open('w', encoding='utf-8', newline='') as f: 105 w = csv.writer(f) 106 w.writerows(texts) 107 108 # 以上です 109 return 110 111 112if __name__ == "__main__": 113 main()

要するに、元ソースは、見出しから見出しまでの間に存在する、同じ階層の行(text)をfor文で読み込みしていって、その行を結合して contents としていたのを、

改良後は、if文で http:(画像へのリンク)が含まれない行は 今までどおり contents で
行に http(画像へのリンク)が含まれる場合においては、リンクが含まれる行として elem_link の変数に入れるようにしました

リンクを含む本文は、elem_link の変数に入れる事ができたのですが、問題は このelem_linkから、href の値となる URLの値だけを抽出するのにはどうしたらいいのでしょうか?

現在の elem_link の中身
「本文テキスト3。<a href='https://.com/.html>https://.com/*.html</a>

URLの値だけにしたい
「https://**.com/.html」

replace やsplit は、list に使えないと警告がでて、どの場面で リンクURLの値を抽出したらいいのかがわからないのですが・・ for文のリスト内の変数elem_link から特定の値を抽出する方法を教えていただけないでしょうか・・

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

BeautifulSoupを使うと以下です。
dataには、「取得するHTML」を入れてください。

python

1from bs4 import BeautifulSoup, element 2 3def get_TTA(h3): 4 texts = [] 5 for sib in [s for s in h3.next_siblings if type(s) == element.Tag]: 6 texts.append(sib.text) 7 a_tag = sib.find('a') 8 if a_tag: 9 return (h3.text, texts, a_tag['href']) 10 11soup = BeautifulSoup(data) 12result = [get_TTA(h3) for h3 in soup.findAll('h3')] 13print(result)

実行結果

python

1>>> print(result) 2[('あああ', ['本文テキスト1。', '本文テキスト2。', '本文テキスト3。https://*****.com/***.html'], 'https://******.com/***.html'), ('いいい', ['本文テキスト4。', '本文テキスト5。https://*****.com/***.html'], 'https://******.com/***.html'), ('ううう', ['本文テキスト6。', '本文テキスト6。', '本文テキスト7\u3000https://*****.com/***.html'], 'https://******.com/***.html')]

投稿2021/08/07 10:05

ppaul

総合スコア24670

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

dub

2021/08/08 05:33

いつもありがとうございます 完璧すぎです!コードをみても、ほとんど理解できなかったので・・頑張ります・・
dub

2021/08/08 06:05 編集

すみません。下記部分・・を反映するにはどうしたらいいでしょうか。 現在 texts に a_tagが含まれるsib.text もtexts に含まれているのですが、 a タグが含まれる sib.text においては、texts から除外したいのです。 (要するに 本文にリンクが含まれる行は text(説明)から除外したい) # 空でなければリストに追加←#■改良部分■ リンクが存在しない行のみtextに追加 if 'http' not in text: contents.append(text) 追記 それと、毎回 悩むのですが 実際のソースにおいては texts に不要な スペースなどが含まれているので消したいのですが、リストタグでは get_text() やstrip()が使えないと警告されます。 このような場合は 最初の 全体のHTMLの時 soup.get_text() などとしておくのでよいのでしょうか?
ppaul

2021/08/08 06:38

for sib in [s for s in h3.next_siblings if type(s) == element.Tag]: a_tag = sib.find('a') if a_tag: return (h3.text, texts, a_tag['href']) texts.append(sib.text) と順序を入れ替えれば良いです。
ppaul

2021/08/08 06:40

get_text() やstrip()が使えないと警告されます。 通常は、for文で回して、各要素について処理します。それでできないものがあれば、質問すれば誰かが教えてくれるでしょう。
dub

2021/08/09 05:00 編集

ありがとうございます! あともう一つだけ・・とことん調べたつもりなのですが解決できない部分があります・ 実際のソースで h3 は下記のように 記号+商品名 +価格+ 本数 がスペースで繋がっているので h3.text の中から 商品名と価格を別の変数に入れて取り出したいと考えました。 実際のH3の例 <h3>○商品名   ¥3,650  2本</h3> ※商品名の手前の記号は ○や☆ ■●▽の5種類存在する このように自分で改良してみましたが・・ for sib in [s for s in h3.next_siblings if type(s) == element.Tag]: a_tag = sib.find('a') item_name=re.search(r'(.*)(?=\)',h3.text) #「¥」以前の文字列を抜き出したが 最初の記号を外したい price=re.search(r'\([\d,]+)',h3.text) #「¥」以降カンマ入りの数字を抽出した if a_tag: return (item_name,price,h3.text, texts, a_tag['href']) texts.append(sib.text) わからない所 ■1 item_name ,price ともに取り出せたのですが、 item_name は <re.Match object; span=(0, 39), match='●商品名 '> price は <re.Match object; span=(45, 51), match='\3,650'> のように re.Match というオブジェクト全体の文字列を取り出してしまうのです。 「商品名」「価格」の値のみ を取り出すのは、どのようにすればいいのかヒントをいただけないでしょうか・・ item_name が「商品名」 price が「3650」と値のみ取り出したい ■2 正規表現で 商品名を抽出する際、 <h3>○商品名   ¥3,650  2本</h3>の ¥ より以前の文字列を取り出す 正規表現、 item_name=re.search(r'(.*)(?=\)',h3.text) としましたが、 最初の記号を外すには どのように 記載すればいいのか・・ ※商品名の手前の記号は ○や☆ ■●▽の5種類存在する
dub

2021/08/09 05:34

本来、別質問すべきだったかもしれませんが・・どういったケースでこの現象(re.match ~) が起きるのかが認識できていないので・・ 見出しタグから正規表現で抜き出すのがダメなのでしょうか?
ppaul

2021/08/09 06:09

最初の質問はBeautifulSoup関連ですので、正規表現関連の質問です。 teratailはQAを蓄積して誰もが技術を見つけることができるものを目指しているらしいので、別の質問にするのが良いでしょう。 なお、追加質問を延々と続ける人は以降の質問に回答がつかなくなることが多いので注意した方がよいです。 >>> h3_text = '<h3>○商品名   \3,650  2本</h3>' >>> item_name = re.search(r'(.*)(?=\)',h3_text) >>> print(type(item_name)) <class 're.Match'> >>> print(item_name.groups()) ('<h3>○商品名\u3000\u3000\u3000',) \u3000は全角空白ですね。 正規表現については、以下を読みましょう。 https://docs.python.org/ja/3/library/re.html https://note.nkmk.me/python-re-match-object-span-group/
dub

2021/08/09 08:35

大変失礼いたしました。以後 別記事にて質問いたします。 参考URLもありがとうございます!下のURLを読んでいたのですが、自分が正規表現でグループ化している事に全く気付いておらず・・・・正規表現の問題だったとは・・ ありがとうございます! 以後気をつけます
guest

0

http://kondou.com/BS4/

python

1data="""<!DOCTYPE html> 2<html lang="ja"> 3<head> 4 <meta charset="UTF-8"> 5 <title>タイトル</title> 6</head> 7<body> 8 <div> 9 <h3>あああ</h3> 10 <p>本文テキスト1。</p> 11 <p>本文テキスト2。</p> 12 <p>本文テキスト3。<a href='https://******.com/***.html'>https://*****.com/***.html</a></p> 13※画像ページへのリンクだが、.htmlとページに画像が埋め込みしてある。 14hrefも画像と同じURLが記載ある 15 16 <h3>いいい</h3> 17 <p>本文テキスト4。</p> 18 <p>本文テキスト5。<a href='https://******.com/***.html'>https://*****.com/***.html</a></p> 19 20 <h3>ううう</h3> 21 <p>本文テキスト6。</p> 22  <p>本文テキスト6。</p> 23  <p>本文テキスト7 <a href='https://******.com/***.html'>https://*****.com/***.html</a></p> 24  。</p> 25   26   27 28 </div> 29</body> 30</html>""" 31 32from bs4 import BeautifulSoup 33soup = BeautifulSoup(data) 34 35for link in soup.find_all('a'): 36 print(link.get('href')) 37""" 38https://******.com/***.html 39https://******.com/***.html 40https://******.com/***.html 41"""

リンクの抽出ならば、BeatifulSoupがやりやすいと思います。

投稿2021/08/07 09:48

toshikawa

総合スコア388

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問