pythonでwebスクレイピングをして、その検索ワードや検索結果をexcelに出力したいと考えています。
excelでの出力の例としてこのような形を構想しています。
以下がコードなのですが、解決策があればご教示お願い致します。
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from urllib import request
from bs4 import BeautifulSoup
import requests
from urllib.parse import urljoin
import openpyxl as op
import datetime
import time
def change_window(browser):
all_handles = set(browser.window_handles)
switch_to = all_handles - set([browser.current_window_handle])
assert len(switch_to) == 1
browser.switch_to.window(*switch_to)
def main():
for i in range(1,9):
wb = op.load_workbook('一般名称.xlsx')
ws = wb.active
word = ws['A'+str(i)].value
driver = webdriver.Chrome(r'C:\/chromedriver.exe')
driver.get("https://www.pmda.go.jp/PmdaSearch/kikiSearch/")
#id検索
elem_search_word = driver.find_element_by_id("txtName")
elem_search_word.send_keys(word)
#name検索
elem_search_btn = driver.find_element_by_name('btnA')
elem_search_btn.click()
change_window(driver)
#print(driver.page_source)
cur_url = driver.current_url
html = driver.page_source
soup = BeautifulSoup(html,'html.parser')
#print(cur_url)
has_pdf_link = False
print(word)
wb = op.load_workbook('URL_DATA.xlsx')
ws = wb.active
ws['C'+str(i)].value = word
for a_tag in soup.find_all('a'):
link_pdf = (urljoin(cur_url, a_tag.get('href')))
#link_PDFから文末がpdfと文中にPDFが入っているものを抽出
#print(word)
if (not link_pdf.lower().endswith('.pdf')) and ('/ResultDataSetPDF/' not in link_pdf):
continue
if ('searchhelp' not in link_pdf):
has_pdf_link = True
print(link_pdf)
ws['B'+str(i)].value = link_pdf
if not has_pdf_link:
print('False')
ws['B'+str(i)].value = has_pdf_link
time.sleep(2)
time_data = datetime.datetime.today()
ws['A'+str(i)].value = time_data
wb.save('URL_DATA.xlsx')
if __name__ == "__main__":
main()
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+2
1, デバックしていませんがコードを見て原因はfor i in range(1,9):
だと思います。
一般名称.xlsx
の行を取得するためのindex
と
URL_DATA.xlsx
に出力するための行番号(index
)で使い回しを行っているからかと。
別の変数としてください。
行単位に出力するならば、listにtupleで格納するのも良いのでは。
2, 日付が各行に出力されない原因は以下のインデントが一段深いです。
time.sleep(2)
time_data = datetime.datetime.today()
ws['A'+str(i)].value = time_data
追記・修正依頼欄に書ききれないので。
a. 初心者の方に多いのですが、質問者さんのように処理を一つの関数にどんどん追加していく人が多いです。
これをしてしまうとなんらかの問題がソースコードに発生したときに、
どこの処理が問題なのかの原因の切り分けが不可能になりやすいです。
今回の件は出力の問題なのでスクレイピングはほぼ関係ないですよね。
でも同じ関数内に書いてしまうとほぼなのでもしかしたら関係あるかもで調査する必要があります。
対策としては適度な関数分割です。
スクレイピングをして、HTMLを取得する部分のコードは以下のようにできます。
こうすることでスクレイピングの処理は関数内で閉じているので、該当の処理は意識しなくても良くなります。
def get_content(word):
"""
スクレイピングする。
:param word 検索キーワード
:return スクレイピング結果(HTML)とURL
※ chromedriver.exe をCドライブ直下に置くこと。
"""
driver = webdriver.Chrome(r'C:\/chromedriver.exe')
driver.get("https://www.pmda.go.jp/PmdaSearch/kikiSearch/")
# id検索
elem_search_word = driver.find_element_by_id("txtName")
elem_search_word.send_keys(word)
# name検索
elem_search_btn = driver.find_element_by_name('btnA')
elem_search_btn.click()
change_window(driver)
# print(driver.page_source)
html = driver.page_source
cur_url = driver.current_url
return html, cur_url
html, cur_url = get_content(word)
一般名称.xlsxから検索キーワードを取得する部分は以下のように(未テスト)
def get_search_keyword():
"""
エクセルファイルを開き、検索キーワードを取得する。
"""
# テスト用
#yield "血液照射装置"
#yield "放射性医薬品合成設備"
from contextlib import closing
with closing(op.load_workbook('一般名称.xlsx')) as wb:
for i in range(1, 9):
ws = wb.active
yield ws['A' + str(i)].value
for i, word in enumerate(get_search_keyword(), start=1):
b, 次に一般名称.xlsx
やURL_DATA.xlsx
のファイルが回答者の環境には無いため実行再現しずらいです。
適当なサンプルデータを質問文に追記していただくか。
もしくはウィンドウズ環境にしかないxlsx
ではなく汎用性のあるデータ構造csv形式など。
この質問はリストに質問文の画像のような形で値を格納したいとも言いかえれます。
こちらなら環境を選ばないので回答が付きやすいかと。
◇不具合
2列目にFalse
が出力される原因はこのコードですね。
has_pdf_link
がBool
型で値がFalse
が設定されています。
if not has_pdf_link:
print('False')
ws['B'+str(i)].value = has_pdf_link
試しにselenium
とopenpyxl
を使わないように出力はリストになるように書き換えてみました。
# -*- coding: utf-8 -*-
from bs4 import BeautifulSoup
from urllib.parse import urljoin
import datetime
import time
#import openpyxl as op
def get_content(word: str):
return """
<table class="SearchResultTable" id="ResultList">
<tbody><tr>
<th scope="col" style="width:13em" nowrap="">一般的名称</th>
<th scope="col" style="width:15em" nowrap="">販売名</th>
<th scope="col" style="width:15em" nowrap="">製造販売業者等</th>
<th scope="col" style="width:13em" nowrap="">添付文書</th>
<th scope="col" style="width:13em" nowrap="">改訂指示<br />反映履歴</th>
<th scope="col" style="width:13em" nowrap="">審査報告書/<br />再審査報告書等</th>
<th scope="col" style="width:13em" nowrap="">緊急安全性情報</th>
</tr>
<tr class="TrColor01">
<td><div><a target="_blank" href="/PmdaSearch/kikiDetail/GeneralList/20500BZZ00241000_A_01">血液照射装置</a></div></td>
<td><div>日立X線照射装置 MBR−1520A−TW</div></td>
<td><div>製造販売/株式会社 日立メディコ</div></td>
<td><div><a href="javascript:void(0)" onclick="detailDisp("PmdaSearch" ,"650053_20500BZZ00241000_A_01_01");">HTML</a><br /><a target="_blank" href="/PmdaSearch/kikiDetail/ResultDataSetPDF/650053_20500BZZ00241000_A_01_01">PDF (2007年12月19日)</a></div></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody></table>
""", "https://www.pmda.go.jp/PmdaSearch/kikiSearch/"
def get_search_keyword():
# テスト用
yield "血液照射装置"
yield "放射性医薬品合成設備"
def parse(soup, cur_url: str):
"""
スクレイピング結果を解析
"""
for a_tag in soup.find_all('a'):
link_pdf = (urljoin(cur_url, a_tag.get('href')))
# link_PDFから文末がpdfと文中にPDFが入っているものを抽出
if (not link_pdf.lower().endswith('.pdf')) and ('/ResultDataSetPDF/' not in link_pdf):
continue
if 'searchhelp' not in link_pdf:
yield True, link_pdf
def main():
for i, word in enumerate(get_search_keyword(), start=1):
html, cur_url = get_content(word)
soup = BeautifulSoup(html, 'html.parser')
output = []
time_data = datetime.datetime.today()
for has_pdf_link, link_pdf in parse(soup, cur_url):
output.append([time_data, link_pdf, word])
print(link_pdf)
print(output)
if __name__ == "__main__":
main()
以下のFalse
の仕様がよく分かりませんでしたので、その部分がうまく実装できてないですがいますが。
質問文の画像を見る限りこういうふうに出力したいのでしょうか。
if not has_pdf_link:
print('False')
ws['B'+str(i)].value = has_pdf_link
time.sleep(2)
time_data = datetime.datetime.today()
ws['A'+str(i)].value = time_data
# -*- coding: utf-8 -*-
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from urllib import request
from bs4 import BeautifulSoup
import requests
from urllib.parse import urljoin
import openpyxl as op
import datetime
import time
def change_window(browser):
all_handles = set(browser.window_handles)
switch_to = all_handles - set([browser.current_window_handle])
assert len(switch_to) == 1
browser.switch_to.window(*switch_to)
def get_content(word):
"""
スクレイピングする。
:param word 検索キーワード
:return スクレイピング結果(HTML)とURL
※ chromedriver.exe をCドライブ直下に置くこと。
"""
driver = webdriver.Chrome(r'C:\/chromedriver.exe')
driver.get("https://www.pmda.go.jp/PmdaSearch/kikiSearch/")
# id検索
elem_search_word = driver.find_element_by_id("txtName")
elem_search_word.send_keys(word)
# name検索
elem_search_btn = driver.find_element_by_name('btnA')
elem_search_btn.click()
change_window(driver)
# print(driver.page_source)
html = driver.page_source
cur_url = driver.current_url
return html, cur_url
def get_search_keyword():
"""
エクセルファイルを開き、検索キーワードを取得する。
"""
# テスト用
#yield "血液照射装置"
#yield "放射性医薬品合成設備"
from contextlib import closing
with closing(op.load_workbook('一般名称.xlsx')) as wb:
for i in range(1, 9):
ws = wb.active
yield ws['A' + str(i)].value
def parse(soup, cur_url: str):
"""
スクレイピング結果を解析
"""
for a_tag in soup.find_all('a'):
link_pdf = (urljoin(cur_url, a_tag.get('href')))
#print(link_pdf)
# link_PDFから文末がpdfと文中にPDFが入っているものを抽出
if (not link_pdf.lower().endswith('.pdf')) and ('/ResultDataSetPDF/' not in link_pdf):
continue
if 'searchhelp' not in link_pdf:
yield True, link_pdf
def output_excel(output:list, row_index: int):
"""
エクセルに出力する。
:param output 行データ
:param row_index 出力するための開始行
"""
#wb = op.load_workbook('URL_DATA.xlsx')
#ws = wb.active
print("#" * 50)
for i, (time_data, link_pdf, word_col) in enumerate(output, start=row_index):
print(i , time_data, link_pdf,word_col)
# ここにエクセルの設定処理を
#wb.save('URL_DATA.xlsx')
def main():
START_ROW = 0
row_index = 1
for word in get_search_keyword():
html, cur_url = get_content(word)
soup = BeautifulSoup(html, 'html.parser')
output = []
time_data = datetime.datetime.today()
for i, (has_pdf_link, link_pdf) in enumerate(parse(soup, cur_url), start=START_ROW):
word_col = ""
if i == START_ROW:
word_col = word
output.append([time_data, link_pdf, word_col])
output_excel(output, row_index)
row_index += len(output)
if __name__ == "__main__":
main()
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 90.00%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
2018/07/23 18:20
いただいた回答をもとに、修正してみます。
関数での分割もどこで分けていいかがわからず敬遠していたのですが、もう一度学び直したいと思います。
また実行再現の件も承知しました。次回からはサンプルデータなどを添付致します。
2018/07/23 18:35 編集
>関数での分割もどこで分けていいか
処理の流れが変わったタイミングです。入力→出力→(この箇所)→出力結果を元に入力
>また実行再現の件
再現しずらいというより、環境が限定されているというのがあって。
selenium&chromedriverが導入済み、Windows OS、エクセルが開ける、Pythonの環境がある。
スクレイピングをエクセルに書き出したいという要件は理解できるのですが、ここまで環境が限定されると回答が付きづらいのです。そのため、Pythonがあれば再現できる汎用的な話題に転換してそれを自分のプログラムに組み込む形のほうがいいです。たとえば、BeautifulSoupにはHTMLを渡せるので、HTML部分をコードに文字列として記載しておくそうすれば、回答者の環境にselenium&chromedriverの部分は不要になります。http://python.zombie-hunting-club.com/entry/2017/11/08/192731
2018/07/23 19:03
お答えいただきありがとうございます。
環境が限定される、確かにその通りです・・・。
この場合はexcelに検索の結果を格納する関数をmainにすればよいのでしょうか?
2018/07/23 19:06
mainに書いてもいいですし。output_excelみたいな関数を作ってもよいかと。
2018/07/23 19:43
質問続きで申し訳ないのですが、mainで関数を実行する場合、回答で頂いた関数の下のコードを実行すれば良いのでしょうか?
2018/07/23 19:45
えと話題が2つあって、話題は関数化の話でしょうか、環境にあまり依存しない書き方のほうでしょうか?
どちらでしょうか。
2018/07/23 19:53
関数化の話です。
2018/07/23 20:00
関数化のほうは、下のほうが関数を呼び出すコードです。
一番最後に記載したコードが関数化して呼び出しているので、回答のコードを参考にしてくださいな。
2018/07/23 21:56
丁寧に何度も回答いただき本当に感謝です。
これを参考に、defに慣れていきたいと思います。
2018/07/23 22:09
解決してよかったですー。
2018/07/24 01:10
リンクが見つからないときにFalseを出力する場合、関数parseのif文を編集すれば可能でしょうか?
2018/07/24 02:06 編集
1件もないということなら、output_excelの前にlistの件数をチェックすれば良いのでは。
if len(output)== 0:
output.append([time_data, "FALASE", word])
output_excel(output, row_index)
2018/07/24 10:46
思った通りの動作が確認できました。
夜分遅くにすいませんでした・・・