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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

URL

URL(ユニフォームリソースロケータ)とは、インターネット上のリソース(Webページや電子メールの宛先等)を特定するための形式的な記号の並びの事を言う。

Q&A

解決済

4回答

1974閲覧

複数のページを効率的にスクレイピングしたいです

yep

総合スコア45

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

URL

URL(ユニフォームリソースロケータ)とは、インターネット上のリソース(Webページや電子メールの宛先等)を特定するための形式的な記号の並びの事を言う。

0グッド

1クリップ

投稿2018/10/15 13:37

編集2018/10/15 14:47

現代俳句データベースの俳句を取得したいと考えています。
しかしながら、以下のコードでは、複数のページを取得するためには、一つの季節だけで何百という記載をしなければならなく非効率的になってしまいます。
もしよろしければ、効率的なスクレイピングの方法をご教授ください。
何卒、よろしくお願いいたします。

現代俳句データベーストップ
http://www.haiku-data.jp/top.php

python

1# -*- coding: utf-8 -*- 2import MeCab 3import codecs 4import re 5import urllib.parse as par 6import urllib.request as req 7import time 8 9 10def write2file(fname, sentences): 11 with codecs.open(fname, 'w', 'utf-8') as f: 12 f.write("".join(sentences)) 13 14def get_morphemes(sentences): 15 morphemes = [] 16 for sent in sentences: 17 if len(sent) == 0: 18 continue 19 temp = tagger.parse(sent).split() 20 temp.append("。\n") 21 morphemes.append(" ".join(temp)) 22 return morphemes if morphemes else -1 23 24tagger = MeCab.Tagger("-Owakati") 25link = "http://www.haiku-data.jp/kigo_list.php?season_cd=1#result" 26fname_list = ["aimaku"] 27word_list = ["藍蒔く"] 28for fname, word in zip(fname_list, word_list): 29 with req.urlopen(link + par.quote_plus(word)) as response: 30 time.sleep(1) 31 html = response.read().decode('utf-8') 32 all_p_tag = re.findall("<a>.+?</a>", html, re.MULTILINE | re.DOTALL) 33 temp = [] 34 for p in all_p_tag: 35 p = re.sub("[\s!-~]*", "", p) 36 p = p.split("。") 37 morphemes = get_morphemes(p) 38 if morphemes == -1: 39 continue 40 temp = temp + morphemes 41 write2file(fname + ".txt", temp)

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

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

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

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

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

退会済みユーザー

退会済みユーザー

2018/10/15 13:46 編集

複数ページを取得する場合、取得する間隔を最頻でも0.5sec~1.0secほどあけるようにしておいたほうが良いと思います。サイトによってはアクセス拒否されることがありますので。
yep

2018/10/15 14:11

ご指摘ありがとうございます。こちらでいかがでしょうか?
退会済みユーザー

退会済みユーザー

2018/10/15 14:13

いいんじゃないでしょうか(質問と違う内容ですみません^^;
yep

2018/10/15 14:15

いえいえ!興味を持っていただき感謝しております。
tiitoi

2018/10/15 15:21

結局質問欄記載のWebサイトのなんの情報が得られればいいのでしょうか?
yep

2018/10/15 21:43 編集

最終的な目標としては、現代俳句データベースに記載されている全俳句を集めたいと思っています。
tiitoi

2018/10/16 04:23

全俳句を集めたいのであれば、検索ではなく、URL の ID パラメータをインクリメントすればよいのでは?手順を回答に記載しました。
guest

回答4

0

ベストアンサー

URL を見ると、http://www.haiku-data.jp/work_detail.php?cd=<ID> となっていますね。データベースで管理しているので、その ID カラムの値が cd パラメータなのでしょう。
よって、これを順番にインクリメントしていけば全俳句を取得できそうです。

  • 存在しないIDの場合は、テーブル欄が空になるのでそれで判別できます。
  • 41000 まではあり、42000 ではなかったので、ID 数の最大はその間の値です。
  • このような事例 もありますので、サーバーに負荷がかからないようにアクセス間隔に注意してください。

サンプルコード

HTML 解析部分

python

1import csv 2import time 3from urllib import request 4from bs4 import BeautifulSoup 5 6def get_data(url): 7 req = request.urlopen(url) 8 soup = BeautifulSoup(req.read(), 'html.parser') 9 10 # 俳句 11 poem_elem = soup.select('td[height=40] b')[0] 12 poem = poem_elem.text.replace('*', '').strip() # サニタイズ 13 if not poem: 14 return False # 存在しないID 15 # 作者 16 author_elem = soup.select('table[cellspacing="1"] tr:nth-of-type(1) td:nth-of-type(2)')[0] 17 author = author_elem.text.strip() # サニタイズ 18 # 季語 19 season_word_elem = soup.select('table[cellspacing="1"] tr:nth-of-type(2) td:nth-of-type(2)')[0] 20 season_word = season_word_elem.text.strip() # サニタイズ 21 # 季節 22 season_elem = soup.select('table[cellspacing="1"] tr:nth-of-type(3) td:nth-of-type(2)')[0] 23 season = season_elem.text.strip() # サニタイズ 24 # 出典 25 source_elem = soup.select('table[cellspacing="1"] tr:nth-of-type(4) td:nth-of-type(2)')[0] 26 source = source_elem.text.strip() # サニタイズ 27 # 前書 28 foreword_elem = soup.select('table[cellspacing="1"] tr:nth-of-type(5) td:nth-of-type(2)')[0] 29 foreword = foreword_elem.text.strip() # サニタイズ 30 31 return {'poem': poem, 'author': author, 'season_word': season_word, 32 'season': season, 'source': source, 'foreword': foreword}

URL 取得部分

python

1data = [] 2MAX_ID = 10 # 登録されているIDの最大値 3for i in range(1, MAX_ID): 4 url = 'http://www.haiku-data.jp/work_detail.php?cd={id}'.format(id=i) 5 print('fetching data... ' + url, end=' ') 6 d = get_data(url) 7 if d: 8 print('result: SUCCESS') 9 data.append(d) 10 else: 11 print('result: MISSING') 12 13 time.sleep(1) # アクセス間隔

CSV 書き込み部分

with open('output.csv', 'w', newline='', encoding='utf-8') as f: writer = csv.DictWriter(f, fieldnames=['poem', 'author', 'season_word', 'season', 'source', 'foreword']) writer.writeheader() writer.writerows(data)

実行結果

fetching data... http://www.haiku-data.jp/work_detail.php?cd=1 result: SUCCESS fetching data... http://www.haiku-data.jp/work_detail.php?cd=2 result: SUCCESS fetching data... http://www.haiku-data.jp/work_detail.php?cd=3 result: SUCCESS fetching data... http://www.haiku-data.jp/work_detail.php?cd=4 result: SUCCESS fetching data... http://www.haiku-data.jp/work_detail.php?cd=5 result: SUCCESS fetching data... http://www.haiku-data.jp/work_detail.php?cd=6 result: SUCCESS fetching data... http://www.haiku-data.jp/work_detail.php?cd=7 result: SUCCESS fetching data... http://www.haiku-data.jp/work_detail.php?cd=8 result: SUCCESS fetching data... http://www.haiku-data.jp/work_detail.php?cd=9 result: SUCCESS

csv

1poem,author,season_word,season 2朝霧の中に九段のともし哉,正岡子規,霧,秋 3あたたかな雨が降るなり枯葎,正岡子規,枯葎,冬 4菜の花やはつと明るき町はづれ,正岡子規,菜の花,春 5秋風や伊予へ流るる汐の音,正岡子規,秋風,秋 6長閑さや障子の穴に海見えて,正岡子規,長閑,春 7若鮎の二手になりて上りけり,正岡子規,若鮎,春 8行く秋をすつくと鹿の立ちにけり,正岡子規,行く秋,秋 9我声の風になりけり茸狩,正岡子規,茸狩,秋 10毎年よ彼岸の入りに寒いのは,正岡子規,彼岸,春

投稿2018/10/16 04:22

編集2018/10/16 11:46
tiitoi

総合スコア21956

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

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

yep

2018/10/16 11:28

サンプルコードまで書いていただいて誠にありがとうございました。
yep

2018/10/16 11:31 編集

しかしながら、cd=23のところでIndexErrorがでてしまいます。 fetching data... http://www.haiku-data.jp/work_detail.php?cd=23 Traceback (most recent call last): File "/home/yudai/Desktop/wikipedia/haiku.py", line 34, in <module> d = get_data(url) File "/home/yudai/Desktop/wikipedia/haiku.py", line 18, in get_data author_elem = soup.select('table[width=85%] tr:nth-of-type(1) td:nth-of-type(2)')[0] IndexError: list index out of range
tiitoi

2018/10/16 11:49 編集

No23 は画像があったので、HTML の構造が想定していたものと少し変わっていたので、エラーになっていました。 回答のコードを修正しました。 数万ある全部のIDを取得して調べたわけではないので、他にもエラーになるIDがもしあった場合は、Webページの構造を見て、soup.select() のCSS セレクタを修正してください。 http://www.htmq.com/csskihon/005.shtml
yep

2018/10/16 12:01 編集

正直、もうできないのではと感じていました。 本当にありがとうございました。 こんなcssのサイトがあるんですね。大変勉強になりました。
guest

0

tiitoiさんのを参考にHTML 解析部分を変更しました

python

1import csv 2import time 3 4import requests 5from bs4 import BeautifulSoup 6 7headers = { 8 'User-Agent': 9 'Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko' 10} 11 12 13def get_data(url): 14 15 r = requests.get(url, headers=headers) 16 17 if r.status_code == requests.codes.ok: 18 19 soup = BeautifulSoup(r.content, 'html.parser') 20 21 try: 22 title = soup.select_one('div.title').get_text(strip=True) 23 24 except: 25 return False 26 27 else: 28 if len(title) == 0: 29 return False 30 31 info = [[ 32 tds.get_text(strip=True).replace('\u3000', '') 33 for tds in trs.find_all('td') 34 ] for trs in soup.find( 35 'table', 36 attrs={ 37 'align': 'center', 38 'bgcolor': '#E7DDD4', 39 'border': '0', 40 'cellpadding': '6', 41 'cellspacing': '1', 42 'width': '85%' 43 }).find_all('tr')] 44 45 result = dict(info) 46 47 result['俳句'] = title 48 49 return result 50 51 52if __name__ == '__main__': 53 54 MAX_ID = 100 # 登録されているIDの最大値 55 56 with open('output.csv', 'w', newline='', encoding='utf-8') as f: 57 writer = csv.DictWriter( 58 f, 59 fieldnames=[ 60 '俳句', '作者', '季語', '季節', '出典', '前書', '評言','評者', '備考' 61 ]) 62 writer.writeheader() 63 64 for i in range(1, MAX_ID): 65 66 url = 'http://www.haiku-data.jp/work_detail.php?cd={id}'.format( 67 id=i) 68 print('fetching data... ' + url, end=' ') 69 70 data = get_data(url) 71 72 if data: 73 print('result: SUCCESS') 74 writer.writerow(data) 75 else: 76 print('result: MISSING') 77 78 time.sleep(1) # アクセス間隔

投稿2018/10/16 13:56

編集2018/10/16 15:30
barobaro

総合スコア1286

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

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

yep

2018/10/16 14:11

素晴らしいです!MISSINGが出力されません!
yep

2018/10/16 14:14

僕の勉強不足で申し訳ないのですが、変更というのは、ユーザーエージェントの記述という事でしょうか?
tiitoi

2018/10/16 14:18

MISSING がでないのは、IDがないページでも中身が空の div.title は存在するからかと
yep

2018/10/16 14:55

なるほど。後で削除すれば、問題ないですが、それは素直にifelseで無くしたほうがいいなと思いました。
barobaro

2018/10/16 15:31

titleが空のときはMISSINGにしました。 変更点は表の左側をキーにtableを一括で取るようにしています。
guest

0

季語だけ別のサイトからスクレイピングするか、あらかじめ季語リストのファイルか何か作っておいて、ファイルから季語を読み出しながら検索するのはどうでしょう。

季語リストのファイルを作るだけなら下記のようなサイトからテキストをコピペして区切り文字を改行等に置換すれば簡単に作れそうです。
季語のようなあまり更新が無いデータなら静的なデータで持っておいても良いかと。
http://kigosai.sub.jp/aiu.html

投稿2018/10/15 17:17

mrkmyki

総合スコア325

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

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

yep

2018/10/15 21:50

確かにこの方法であれば、大部分の俳句を検索でき、重複した俳句を削除すればかなり取れると思います。
yep

2018/10/15 21:52

このwebサイトの構成ですべてをスクレイピングするのは、かなり難しいのでしょうか?
mrkmyki

2018/10/15 22:27

> このwebサイトの構成ですべてをスクレイピングするのは、かなり難しいのでしょうか? 全ての俳句一覧が取れないため、このサイトの構成では難しそうに思えますね。
guest

0

一つの季節だけで何百という記載をしなければならなく非効率的になってしまいます。

というのは、リストに季語を追加するのが大変だという意味であっていますでしょうか。
季語ではなく季節で検索するようにすれば楽になると思います(検索ページのキーワードの項目を利用します)。
ただ、検索結果が複数ページに及ぶため、それを処理するロジックが新たに必要ですが。

また、検索結果の俳句のページに飛ぶと、季節の項目があると思います。
おそらく検索がここに引っかかっているので、ここが空のものは逆に引っ掛かりませんのでご注意ください。

投稿2018/10/15 14:36

退会済みユーザー

退会済みユーザー

総合スコア0

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

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

yep

2018/10/15 14:46

ご回答ありがとうございました。やはり、季節で検索するようにできれば、楽なのですが、かなりの複数ページのため、僕にはどのように効率的にすればよいかロジックがわかりませんでした。
yep

2018/10/15 14:52

検索ページのキーワードの項目ですが、季節名を入れるだけではやはり「春」全体の俳句は検索できませんので、ややこしいかと。
退会済みユーザー

退会済みユーザー

2018/10/16 02:40 編集

季語で検索する場合でも、検索結果が複数ページに及ぶ場合があると思います。なので、その場合にも対応できるロジックは必要かと。方法としては、複数ページあった場合、次のページのURLを取得、次のページを取得、とあるだけ取得する方法があります。
退会済みユーザー

退会済みユーザー

2018/10/16 02:44

検索結果の俳句をクリックするとわかるのですが、リンク先に飛ぶと季節という項目があると思います。ここが春であれば、キーワード検索「春」に引っかかるので、ちゃんと設定されていれば引っかかるかなと思います。ただ、季節以外の項目に春という文字があった場合でもひっかかるので、検索結果は重複しますが。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問