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

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

ただいまの
回答率

90.50%

  • Python

    7998questions

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

  • Python 3.x

    6409questions

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

  • Excel

    1536questions

    Excelは、マイクロソフト社が開発しているデータ集計や分析を行う表計算ソフトの一つです。文書作成や表計算、資料作成などの多彩な機能を備えており、統合パッケージであるMicrosoft Officeに含まれています。

pythonでの結果をexcelに出力する方法

解決済

回答 1

投稿 編集

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

dkymmmmmt

score 16

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ページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 1

checkベストアンサー

+1

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, 次に一般名称.xlsxURL_DATA.xlsxのファイルが回答者の環境には無いため実行再現しずらいです。
適当なサンプルデータを質問文に追記していただくか。
もしくはウィンドウズ環境にしかないxlsxではなく汎用性のあるデータ構造csv形式など。

この質問はリストに質問文の画像のような形で値を格納したいとも言いかえれます。
こちらなら環境を選ばないので回答が付きやすいかと。


◇不具合
2列目にFalseが出力される原因はこのコードですね。
has_pdf_linkBool型で値がFalseが設定されています。

if not has_pdf_link:
        print('False')
        ws['B'+str(i)].value = has_pdf_link

試しにseleniumopenpyxlを使わないように出力はリストになるように書き換えてみました。

# -*- 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(&quot;PmdaSearch&quot; ,&quot;650053_20500BZZ00241000_A_01_01&quot;);">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()

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/07/23 18:20

    回答ありがとうございます。
    いただいた回答をもとに、修正してみます。
    関数での分割もどこで分けていいかがわからず敬遠していたのですが、もう一度学び直したいと思います。
    また実行再現の件も承知しました。次回からはサンプルデータなどを添付致します。

    キャンセル

  • 2018/07/23 18:35 編集

    @dkymmmmmtさんへ
    >関数での分割もどこで分けていいか
    処理の流れが変わったタイミングです。入力→出力→(この箇所)→出力結果を元に入力
    >また実行再現の件
    再現しずらいというより、環境が限定されているというのがあって。
    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

    @umyuさん
    お答えいただきありがとうございます。
    環境が限定される、確かにその通りです・・・。
    この場合はexcelに検索の結果を格納する関数をmainにすればよいのでしょうか?

    キャンセル

  • 2018/07/23 19:06

    @dkymmmmmtさんへ
    mainに書いてもいいですし。output_excelみたいな関数を作ってもよいかと。

    キャンセル

  • 2018/07/23 19:43

    @umyuさん
    質問続きで申し訳ないのですが、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

    @dkymmmmmtさんへ
    解決してよかったですー。

    キャンセル

  • 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

    ありがとう御座います。
    思った通りの動作が確認できました。
    夜分遅くにすいませんでした・・・

    キャンセル

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

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

関連した質問

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

  • Python

    7998questions

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

  • Python 3.x

    6409questions

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

  • Excel

    1536questions

    Excelは、マイクロソフト社が開発しているデータ集計や分析を行う表計算ソフトの一つです。文書作成や表計算、資料作成などの多彩な機能を備えており、統合パッケージであるMicrosoft Officeに含まれています。