芋の独り言

当ブログへのアクセスは当ブログのプライバシーポリシーに同意したものとみなします.

インターネットから画像をクローリング・スクレイピング

http://~/1”,” http://~/2”・・・と連番になっているサイトから画像をダウンロードするのに有効なツール,というかスクリプトです.個人的に使ってます. スクリプトをいじってくれれば,応用できるかと思いますね.
まぁ,簡単なスクリプトの説明としては,wxPythonGUIの役割をし,requestでサイトにアクセス,BeautifulSoupで画像データを取得という感じです.

追記:相対パスのトコでも取れるように改良しました.通常のサイトがターゲットの場合,ページ数は入れなくていいです.でも,取って来れない画像もいくつかあります...サイトの背景になってるものはとれないかもしれません

#-*- coding: utf-8 -*-
import requests,os,sys,re 
from bs4 import BeautifulSoup # htmlを読み込むためBeautifulSoupをインポート
from time import sleep
import wx
import ctypes

# 保存先フォルダを選択
def folda_c(event):
    global folder_path
    # フォルダ選択ダイアログを作成
    folder = wx.DirDialog(None,style=wx.DD_CHANGE_DIR | wx.OK | wx.STAY_ON_TOP,message="保存先フォルダ")
    # フォルダが選択されたとき
    if folder.ShowModal() == wx.ID_OK:
        folder_path = folder.GetPath()
    folder.Destroy()
    choose_text.SetLabel(folder_path)

# request.get,proxyあり・なしをまとめたもの
def req_get(url):
    proxy=edit_proxy.GetValue()
    if len(proxy)>4: # proxyがある場合(Noneと文字が入っていなければ?)
        #proxy=re.sub(r"(http:)|(https:)","",proxy)
        proxy_dict={"http":proxy,"https":proxy}
        return requests.get(url,allow_redirects=False,proxies=proxy_dict)
    else: # 普通にrequests.get()する
        return requests.get(url,allow_redirects=False)

# 文字をウィンドウに表示
def now_status(text):
    # ステータスバーに文字列を表示させる
    frame.SetStatusText(text)

# フォルダを作成
def folda_m(tpath):
    # フォルダがカレントディレクトリにない場合に作成する
    try:
        os.mkdir(tpath) # 画像ファイルを入れるフォルダを作成
        print("フォルダを作成しました.")
    except FileExistsError: # 画像ファイルが既に作成されている場合
        if len(os.listdir(tpath)) > 0:
            # フォルダ内のファイルをすべて削除
            for file in os.listdir(tpath):
                os.remove(tpath+"/"+file)
        else:
            pass
    except OSError:
        return False
        print("フォルダは既に作成されていました.")

# 画像のリンク先を取得し,指定のフォルダで画像ファイルを作成する関数
def img(data,tpath,p):
    global count # global宣言すれば代入可能
    soup = BeautifulSoup(data.content,'html.parser') # bsでURL内を解析
    sleep(0.5)
    print(p) # 確認用
    for link in soup.find_all("img"): # imgタグを取得しlinkに格納
        # 相対パスの場合は省略する
        check=link.get("src")
        if re.search(r'https*',check)==None:
            print('相対パス')
            head=soup.find("head").link.get("href")+'/'
            print(head+link.get("src"))
            if link.get("src").endswith(".jpg"): # imgタグ内の.jpgであるsrcタグを取得
                try:
                    res = req_get(head+link.get("src"))
                except ConnectionError:
                    print('ConnectionError')
                    return True
                try:
                    with open(tpath + '/' + link.get("src").split('/')[-1], 'wb') as f: 
                        f.write(res.content)
                except FileNotFoundError:
                    print('FileNotFoundError')
                    return True
            elif link.get("src").endswith(".png"): # imgタグ内の.pngであるsrcタグを取得
                try:
                    res = req_get(head+link.get("src"))
                except ConnectionError:
                    print('ConnectionError')
                    return True
                try:
                    with open(tpath + '/' + link.get("src").split('/')[-1], 'wb') as f: 
                        f.write(res.content)
                except FileNotFoundError:
                    print('FileNotFoundError')
                    return True
            elif link.get("src").endswith(".gif"): # imgタグ内の.gifであるsrcタグを取得
                try:
                    res = req_get(head+link.get("src"))
                except ConnectionError:
                    print('ConnectionError')
                    return True
                try:
                    with open(tpath + '/' + link.get("src").split('/')[-1], 'wb') as f: 
                        f.write(res.content)
                except FileNotFoundError:
                    print('FileNotFoundError')
                    return True
            elif link.get("src").endswith(".svg"): # imgタグ内の.svgであるsrcタグを取得
                try:
                    res = req_get(head+link.get("src"))
                except ConnectionError:
                    print('ConnectionError')
                    return True
                try:
                    with open(tpath + '/' + link.get("src").split('/')[-1], 'wb') as f: 
                        f.write(res.content)
                except FileNotFoundError:
                    print('FileNotFoundError')
                    return True
            sleep(0.1)
            continue
        else:
            pass
        
        if p==True: # 連番サイトのとき
            if link.get("src").endswith("top.jpg"):
                continue
            elif link.get("src").endswith(".jpg"): # imgタグ内の.jpgであるsrcタグを取得
                try:
                    res = req_get(link.get("src"))
                except ConnectionError:
                    print('ConnectionError')
                    return True
                try:
                    with open(tpath + '/' + 'no' + str(count) +'.jpg', 'wb') as f: 
                        f.write(res.content)
                except FileNotFoundError:
                    print('FileNotFoundError')
                    return True
                count=count+1
            elif link.get("src").endswith(".png"): # imgタグ内の.pngであるsrcタグを取得
                try:
                    res = req_get(link.get("src"))
                except ConnectionError:
                    print('ConnectionError')
                    return True
                try:
                    with open(tpath + '/' + 'no' + str(count) +'.png', 'wb') as f: 
                        f.write(res.content)
                except FileNotFoundError:
                    print('FileNotFoundError')
                    return True
                count=count+1
        else: # 上記以外(つまり通常時)        
            if link.get("src").endswith(".jpg"): # imgタグ内の.jpgであるsrcタグを取得
                try:
                    res = req_get(link.get("src"))
                except ConnectionError:
                    print('ConnectionError')
                    return True
                try:
                    with open(tpath + '/' + link.get("src").split('/')[-1], 'wb') as f: 
                        f.write(res.content)
                except FileNotFoundError:
                    print('FileNotFoundError')
                    return True
            elif link.get("src").endswith(".png"): # imgタグ内の.pngであるsrcタグを取得
                try:
                    res = req_get(link.get("src"))
                except ConnectionError:
                    print('ConnectionError')
                    return True
                try:
                    with open(tpath + '/' + link.get("src").split('/')[-1], 'wb') as f: 
                        f.write(res.content)
                except FileNotFoundError:
                    print('FileNotFoundError')
                    return True
            elif link.get("src").endswith(".gif"): # imgタグ内の.gifであるsrcタグを取得
                try:
                    res = req_get(head+link.get("src"))
                except ConnectionError:
                    print('ConnectionError')
                    return True
                try:
                    with open(tpath + '/' + link.get("src").split('/')[-1], 'wb') as f: 
                        f.write(res.content)
                except FileNotFoundError:
                    print('FileNotFoundError')
                    return True
            elif link.get("src").endswith(".svg"): # imgタグ内の.svgであるsrcタグを取得
                try:
                    res = req_get(head+link.get("src"))
                except ConnectionError:
                    print('ConnectionError')
                    return True
                try:
                    with open(tpath + '/' + link.get("src").split('/')[-1], 'wb') as f: 
                        f.write(res.content)
                except FileNotFoundError:
                    print('FileNotFoundError')
                    return True
                
            # 実際に画像をダウンロードした時の名前をそのURLのHTMLで検索をかけると,見つかるよ!
            # Webサイトで右クリック→「ソースの表示」を選択すると,HTML形式で見ることができ,検索とかできる
            if link.get("data-src") != None:
                if link.get("data-src").endswith(".jpg"): # imgタグ内の.jpgであるsrcタグを取得
                    try:
                        res = req_get(link.get("data-src"))
                    except ConnectionError:
                        print('ConnectionError')
                        return True
                    try:
                        with open(tpath + '/' + link.get("data-src").split('/')[-1], 'wb') as f: 
                            f.write(res.content)
                    except FileNotFoundError:
                        print('FileNotFoundError')
                        return True
                elif link.get("data-src").endswith(".png"): # imgタグ内の.pngであるsrcタグを取得
                    try:
                        res = req_get(link.get("data-src"))
                    except ConnectionError:
                        print('ConnectionError')
                        return True
                    try:
                        with open(tpath + '/' + link.get("data-src").split('/')[-1], 'wb') as f: 
                            f.write(res.content)
                    except FileNotFoundError:
                        print('FileNotFoundError')
                        return True
        
    sleep(0.5)



# 画像ダウンロード・保存処理をまとめた関数
def scrape_save(URL1):
    global num

    # プログレスバーを作成
    db=wx.ProgressDialog('作成中',"{0:05.1f}%完了".format(0),100,style= wx.PD_ELAPSED_TIME)
    db.ShowModal()

    # タイトルを自動取得
    d=req_get(URL1)
    soup = BeautifulSoup(d.content,'html.parser')
    t1=soup.title.string
    if re.search(r'所望の連番サイトを書いてください',URL1)!=None:
        pic=True
        t2=t1.split('] ')
        t3=t2[1]
        title=re.sub(r'( | )*\[[0-9]+P','',t3)
        title=re.sub(r'\[.+','',title)
        title=re.sub(r' | ','',title)
    else:
        pic=False
        title=t1
        title=re.sub(r' | ','',title)
    print(pic) # 確認用

    # 保存先のパス作成
    folder_path
    a='/'
    b='\\' # Windowsの場合
    fpath=folder_path.replace(b, a)
    tpath=fpath+'/'+title
    
    if folda_m(tpath) == False:
        tpath=fpath+'/'+'nonetitle'
        folda_m(tpath)
    
        
    data1 = req_get(URL1)
    if img(data1,tpath,pic):
        print('エラーが起きたのでやり直します')
        wx.MessageBox('エラーが起きたのでやり直します','エラー',wx.STAY_ON_TOP)
        return True
    result="{0:5.1f}%完了".format(1*100/page_all)
    print(result)
    db.Update(1*100/page_all,result)

    while True: 
            if pic==False:
                break
            URL=URL1 + "/" +str(num)+ "/"
            data = req_get(URL)
            if (int(data.status_code) >= 500):
                sleep(60)
                continue
            if (int(data.status_code) >= 300):
            # status_codeをチェックすることで、HTTPステータスコードを確認できる.
            # 300以上ならリンク先が見つからなかったとして,ループを抜ける.
                break
            if img(data,tpath,pic):
                print('エラーが起きたのでやり直します')
                wx.MessageBox('エラーが起きたのでやり直します','エラー',wx.STAY_ON_TOP)
                continue
            result="{0:5.1f}%完了".format(num*100/page_all)
            print(result) # 確認用
            db.Update(num*100/page_all,result)
            num=num+1

    
    res="「"+title+"」の作成完了"
    print(res) # プログラムの終了確認
    # スタンバイへの移行抑止を解除する
    ctypes.windll.kernel32.SetThreadExecutionState( ES_CONTINUOUS)
    db.Destroy()

    dial = wx.MessageDialog(None,"作成したファルダを確認するには「OK」を,\nしないで終了する場合は「キャンセル」をクリックしてください", res , wx.OK | wx.CANCEL )
    dial_bt=dial.ShowModal()
    if dial_bt==wx.ID_OK:
        dial.Destroy()
        a='/'
        b='\\' # Windowsの場合
        path=tpath.replace(a, b)
        os.popen('explorer "%s" ' % path)
    elif dial_bt==wx.ID_CANCEL:
        dial.Destroy()
        
    # ステータスバーに文字列を表示させる
    frame.SetStatusText(res)
    return False


def spinbutton_value_change(event):
    obj = event.GetEventObject()
    global page_all
    page_all=int(obj.GetValue())
    global input_page
    input_page.SetValue(str(page_all))


# 本プログラムの処理まとめ
def pros(event):
    if folder_path=="保存先が選択されていません.ボタンをクリックして保存先を選択してください.":
        wx.MessageBox('保存先が選択されていません.選択してから実行してください.','エラー',wx.STAY_ON_TOP)
        return
    else:
        pass
    # スタンバイモードへの移行抑制
    ctypes.windll.kernel32.SetThreadExecutionState(ES_SYSTEM_REQUIRED | ES_CONTINUOUS)
    # ボタンが押せない状態(無効)
    button.Disable()
    choose_button.Disable()
    editbox.Disable()
    edit_proxy.Disable()

    try:
        URL1 = editbox.GetValue()

        global count
        count=1 # グローバル変数
        global num
        num=2 # ループのカウント(2番目から)
    
        while scrape_save(URL1):
            continue
        # 既存のテキスト値を全て消去
        editbox.Clear()

    # 何も入力されてなかったり,無効なURLだった時,処理を行わずやり直す    
    except requests.exceptions.MissingSchema:
        wx.MessageBox('何も入力されてなかったり,無効なURLだったので,再入力してください.','エラー',wx.STAY_ON_TOP)
    except ConnectionResetError:
        wx.MessageBox('ConnectionResetErrorが発生しました.やり直してください.','エラー',wx.STAY_ON_TOP)
    except requests.exceptions.ConnectionError as rc:
        print(str(rc))
        now_status('Error')
    finally:
        # ボタンが押せる状態(有効)
        button.Enable()
        choose_button.Enable()
        editbox.Enable()
        edit_proxy.Enable()
        


# プログラム本体
# 値の初期化(グローバル変数)
ES_CONTINUOUS        = 0x80000000
ES_AWAYMODE_REQUIRED = 0x00000040
ES_SYSTEM_REQUIRED   = 0x00000001
ES_DISPLAY_REQUIRED  = 0x00000002
count=0
num=0
folder_path="保存先が選択されていません.ボタンをクリックして保存先を選択してください."
page_all=1
# アプリケーションのオプジェクトを生成
app=wx.App()
# 重要なコンテナウィジェット,ウィジェットの階層で頂点
# 第一引数:親ウィンドウ(Noneで持たないことを示す),第二引数:識別子(基本-1)
# 第三引数:タイトルとして表示する文字列
# posには表示位置,sizeにはサイズを指定できる←(300,300)というように指定
frame=wx.Frame(None, id=wx.ID_ANY, title='img_downloader(Windows)',size=(640,415))
# ステータスバーを使用する
frame.CreateStatusBar()

# ボタンなどを配置する(直接Frameに張り付けられるが)のに使用
panel1=wx.Panel(frame,wx.ID_ANY,pos=(0,0),size=(640,100))
panel2=wx.Panel(frame,wx.ID_ANY,pos=(0,100),size=(640,20))
panel3=wx.Panel(frame,wx.ID_ANY,pos=(0,120),size=(640,30))
panel4=wx.Panel(frame,wx.ID_ANY,pos=(0,150),size=(640,20))
panel5=wx.Panel(frame,wx.ID_ANY,pos=(0,170),size=(640,30))
panel6=wx.Panel(frame,wx.ID_ANY,pos=(0,200),size=(640,20))
panel7=wx.Panel(frame,wx.ID_ANY,pos=(0,220),size=(640,30))
panel8=wx.Panel(frame,wx.ID_ANY,pos=(0,250),size=(640,20))
panel9=wx.Panel(frame,wx.ID_ANY,pos=(0,270),size=(640,30))
panel10=wx.Panel(frame,wx.ID_ANY,pos=(0,300),size=(640,25))
panel11=wx.Panel(frame,wx.ID_ANY,pos=(0,325),size=(640,50))

# 文字列の表示→説明文を表示する
text="""
URLを入力して,そのURLのサイトからそのサイトにある全てのJPEG・PNG画像を自動でダウンロードします.\n
以下にURLを入力して,実行ボタンをクリックすると,ダウンロードが始まります.\n
proxyが設定してある場合は,proxyのURLを入力してください.\n
※実行中はスリープモードを抑制します.
"""
experiment=wx.StaticText(panel1,wx.ID_ANY,text,style=wx.TE_CENTER,size=(640,100)) # CENTERで中央寄せ

# URLを入力
ex1=wx.StaticText(panel2,wx.ID_ANY,"URLを入力してください:",style=wx.TE_CENTER,size=(640,20))
# tkinterでいうEntryウィジェットの作成
editbox=wx.TextCtrl(panel3,id=wx.ID_ANY,size=(640,25))

# proxyを入力
ex2=wx.StaticText(panel4,wx.ID_ANY,"proxyが設定されている場合は入力してください:",style=wx.TE_CENTER,size=(640,20))
edit_proxy=wx.TextCtrl(panel5,id=wx.ID_ANY,size=(640,25))

# page数を入力
ex3=wx.StaticText(panel6,wx.ID_ANY,"ページ数を入力してください:",style=wx.TE_CENTER,size=(640,20))
input_page=wx.TextCtrl(panel7,id=wx.ID_ANY,size=(640,25))
input_page.Disable()
input_page.SetValue(str(page_all))
spin_button = wx.SpinButton(panel8, style=wx.SP_HORIZONTAL,size=(640,20))
spin_button.Bind(wx.EVT_SPIN, spinbutton_value_change)

# 保存先を選択
choose_text = wx.StaticText(panel9, wx.ID_ANY, folder_path)
choose_button = wx.Button(panel10, wx.ID_ANY, "フォルダの選択",size=(100,25))
choose_button.Bind(wx.EVT_BUTTON,folda_c)

# tkinterでいうButtonウィジェットの作成
button=wx.Button(panel11,id=wx.ID_ANY,label='実行する',size=(100,25))
# wx.EVT_BUTTON(ボタンがクリックされた)ときに実行する関数を第二引数に指定
button.Bind(wx.EVT_BUTTON,pros)
# ツールチップ設定
#button.SetToolTip('クリックして実行')


# Show() メソッドを呼び出してスクリーンに表示←tkinterのpack()やgrid()
frame.Show()
# イベント待ち状態へ遷移←tkinterのmainloop()
app.MainLoop()

参考サイト

  1. wxPythonというツールでGUIを作る
    1. wxPython | Python-izm
    2. wxPython チュートリアル
    3. python入門ブログ: pythonでGUIツールを作る ~進捗ダイアログ~
    4. wxPython - Layout(レイアウト) | まくまくPythonノート
    5. wxPythonで、メッセージダイアログ - naritoブログ
    6. Python/wxPythonによるWindowsGUIプログラミング - labs.beatcraft.com
    7. wxPython(Phoenix)で始めるGUIプログラミング【ボタン】 - デザインのメモ
    8. wxFrame
  2. スリープなどを抑止するためにCで書かれたWindows APIを使用する
    1. Python で Windows のスリープを抑止するコンテキストマネージャ書いた - 破棄されたブログ
    2. Windows previous versions documentation | Microsoft Docs
    3. Python から Windows API を呼び出す - MessageBox の例 - Python 入門
    4. スクリーンセーバーと、スタンバイ抑止プログラム
  3. エクスプローラーでファルダを開く
    1. [Python] スクリプト実行ディレクトリを絶対パスで取得する - Qiita
    2. [Python] ファイルやディレクトリ操作まとめ - YoheiM .NET
    3. Python §56 : tkinterの勉強(フォルダー記録アプリ)ファイルダイアログ | hitochan007のブログ(Pythonの勉強ブログ)
  4. その他
    1. wxPython、フォルダ選択ダイログの作成 - naritoブログ
    2. 認証プロキシ環境でpythonのRequestsライブラリを使用する方法 - メモめもメモ
    3. PythonとBeautiful Soupでスクレイピング - Qiita
    4. これだけは覚えたい!絶対パスと相対パスの違いとは | TechAcademyマガジン

他に参考にしたサイトの”https://qiita.com/kirsch874/items/8709b89cf0fe225a57c1”は今はないかもしれないです...