大量に安全データシート(SDS)をスクレイピングし保存する！(python)

有機化学の研究者をはじめ、ごく一部の方にとって非常に有意義なプログラムになると思います。

##背景##
化学実験で用いる全ての試薬には、それぞれに対応した安全データシート(SDS)なるものがあります。
その管理は安全衛生上、非常に大事なものです。しかし、有機系の研究室ともなれば数百、いや下手したら数千もの試薬を所持しています。その膨大なSDSについて毎年調べるのは正直面倒くさい。。
そんな研究者は日本全国きっと多いはずです。
なので、今回SDSの探索を自動化しました！

##概要##
タイトルのまんまです。
入力した試薬のSDSのPDFデータを調べ、保存するプログラムです。

##実行環境＆使用したもの##
windows10
anaconda(Python3)Spyder　(GoogleColab及びJupyterでも実行可能)
サイト：日本試薬協会（https://www.j-shiyaku.or.jp/Sds ）
ライブラリ：selenium、BeautifulSoup、chromeDriver　etc..

##ソースコード##
ライブラリのインストールがされてれば、コピペで行ける（はず）。
長いのであんまり説明する気にならないです。
でも、難しい構文は使ってないので、皆さんならきっと大丈夫でしょう。
ややこしいのは再帰処理くらいかな、と。


```searchSDS.py
import time
import openpyxl
from openpyxl.styles import PatternFill
from bs4 import BeautifulSoup#解析用。非常に高速
import requests
import re
from selenium import webdriver#web操作用。いちいちchromeを展開するので遅い
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
import urllib.request as req#URL-->PDF保存用
import urllib
#from google.colab import drive
#drive.mount('/content/drive')
#↓に"Mounted at /content/drive"と出力されればgoogleDriveと接続完了

def changeComp(compNo,key,newUrl,sheet):
    global Row
    options = webdriver.ChromeOptions()
    options.add_argument('--headless')
    options.add_argument('--no-sandbox')
    options.add_argument('--disable-dev-shm-usage')
    driver = webdriver.Chrome('chromedriver') #visible
    #driver = webdriver.Chrome('chromedriver',options=options) #NOT visible
    driver.implicitly_wait(10) #
    driver.get(newUrl[compNo])
    #検索テキストボックスの要素をId属性名から取得&文字列を(key)入力
    driver.find_element_by_id("InputWord").clear()
    driver.find_element_by_id("InputWord").send_keys(key)
    #class名から検索ボタを指定しクリック
    driver.find_element_by_class_name('btn_c_submit').click()
    #keyの検索結果数取得
    searchNo=int(re.sub(r"\D", "", driver.find_element_by_class_name("kakoi1").text))
    if searchNo==0 and (compNo+1)<len(newUrl):
        driver.quit()
        return changeComp(compNo+1,key,newUrl,sheet)#ここで再起処理
    elif searchNo>0:
        sheet.cell(row=Row, column=2).value=key
        sheet.cell(row=Row, column=3).value=searchNo 
        saveURL(driver,key,sheet,compNo)           
        driver.quit()
        return True
    
    driver.quit()
    return False#全て0の場合

def saveURL(driver,key,sheet,compNo):
    global Row,companys,urlHead,cNo
    cas=[]
    print(key + " is found!")
    
    #次ページがあれば繰り返し
    while True:
        urlPdf=[]
        urlPdf=driver.find_elements_by_class_name('clickable')
        #print((urlPdf))
        nextButton=driver.find_elements_by_name("submit2")
        pageNo=len(nextButton)
        
        for i in range(len(urlPdf)):
            text=urlPdf[i].text.split("\n")
            #print(text)
            for j in range(len(cas)):
                if cas[j]==text[2+cNo].split(" ")[0]:
                    break
            else:
                try:               
                    cas.append(text[2+cNo].split(" ")[0])#cas
                except IndexError:
                    #print(111)
                    cNo-=1
                    cas.append(text[2+cNo].split(" ")[1])#cas
                sheet.cell(row=Row, column=4).value=text[0+cNo]#name
                if text[0+cNo]==key:
                    sheet.cell(row=Row, column=4).fill=openpyxl.styles.PatternFill(patternType='solid',fgColor='FF0000', bgColor='FF0000')
                sheet.cell(row=Row, column=5,).value=text[1+cNo]#enname
                sheet.cell(row=Row, column=6).value=cas[-1]
                sheet.cell(row=Row, column=7).hyperlink=(urlHead + urlPdf[i].get_attribute("data-href"))#enname
                #URLの保存
                #urllib.request.urlretrieve("https://www.j-shiyaku.or.jp/pdf/public/sds/015/04349.pdf",'save.pdf' )
                sheet.cell(row=Row, column=8).value=companys[compNo]
                Row+=1
                #print(" ")
                continue
            #break
        
        #次ページがあｒばクリック
        if pageNo==1:
            break
        elif pageNo==2:
            if nextButton[1].get_attribute("value")=="前の20件を表示":
                break
            nextButton[1].click()
        elif pageNo==3:
            nextButton[2].click()
    
    
def main(Pass):
    global Row,companys,urlHead
    start = time.time()
    
    #keyの格納
    #データの入出力用xlsxシートの定義
    wb = openpyxl.load_workbook(Pass)
    sheet = wb['Sheet1']
    for r in sheet.iter_rows(min_row=Row, min_col=2, max_row=sheet.max_row, max_col=8):
      for cell in r:
          cell.fill = PatternFill(fill_type = None)
          cell.value=None
    i=2
    keywords=[]
    while True:
        word=sheet.cell(row=i, column=1).value
        if word is None or word =="":break
        keywords.append(word)
        i+=1
    
    hitNo=0
    #print(res.text)  #全html参照可
    res = requests.get(url)
    soup = BeautifulSoup(res.text, "lxml")#lxmlが高速
    elems = soup.find_all(href=re.compile("Sds/Search"))
    
    #相対URLーー＞絶対URｌ変換用。取得した各会社のurlの前半部分は省略されているので。
    urlHead="https://www.j-shiyaku.or.jp"
    
    #各会社のBeautifulSoupを生成
    newUrl,newRes,newSoup=[],[],[]
    for j in range(len(companys)):
        for i in range(len(elems)):
            if companys[j] in str(elems[i].contents[1]):
                newUrl.append(urlHead + elems[i].attrs['href'])
                newRes.append(requests.get(newUrl[-1]))
                newSoup.append(BeautifulSoup(newRes[-1].text, "lxml"))#lxmlが高速
    
    for key in keywords:
        """
        ここでkeyの全角半角の変換などをしたいが、mojimojiのinstallが難。まぁしなくてもよい。
        """
        #changeCompは再起関数。URL見つけるまで終わらない。第一引数はcompNo=0(初期値)
        if not changeComp(0,key,newUrl,sheet):
            #changeComp=False-->全ての会社で検索結果なし
            print(key + " is NOT found.")
            print(" ")
            #Excel書き込み
            sheet.cell(row=Row, column=2).value=key
            sheet.cell(row=Row, column=3).value=0
            Row+=1
            continue
        
        hitNo+=1
    #print(hitNo)
    print("!!All Done!!" + "\n")
    wb.save(Pass)
    wb.close()
    
    elapsedTime = time.time() - start
    print("elapsedTime= {0}".format(round(elapsedTime,2)) + " /sec")

if __name__=="__main__":
    #保存したいURL先。フルパスでもOK
    #Pass="/content/drive/MyDrive/Colab Notebooks/test.xlsx"
    #Pass="template.xlsx"
    #print("左ペイン> ファイル > drive > My Drive からファイル上で左クリックして フルパスをコピーし、")
    Pass=input("フルパスを入力してください。：")
    companys=["富士フイルム","関東化学","昭和"]
    #日本試薬協会URL。ただ時々サイトの装丁が変更されているので注意が必要。
    url = "https://www.j-shiyaku.or.jp/Sds"
    cNo=0 #補正用。
    #開始行
    Row=2

    main(Pass)
```


##jupyterもしくはGoogle Colabで実行したい場合##
今回１番困ったことですが、なぜかJupyter環境で実行すると、取得した配列データが１個ズレる。（どなたか理由を教えてください。）
どうしようもないので、補正値をつくってごまかしました。
最後、cNo=1に変更してからJupyter環境で実行してください。

あと、GoogleColabの場合は適宜コメントアウトを消してください。

##使い方##
①入出力用Excelファイル↓を適当なところに保存。
②"A2"セルから下方向に調べたい試薬を記入
③上書き保存して閉じる。
④ソースコード最終ブロックの、companys配列に調べてほしい会社名順に代入。何個でもよい。
⑤実行。フルパスを入力する。

実行前
![searchSDS処理前.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2004991/1b2c6985-4d31-7fa5-272d-15f0ccbb5f7a.png)
実行後  　　　　　　　　　　　　　　　↓↓↓↓↓↓
![searchSDS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2004991/b4fd7aa9-fb88-7790-cb7c-82d91e6ac553.png)

こんな感じで、各試薬で検索してヒットした試薬（CAS.Noで重複は除いている。）を調べてきます。
URL欄をクリックすれば各試薬のSDSのPDFデータに飛びます。

以上！！