すばらしいfind/47サイトの画像をPythonを使って取得する(その1/2: 対象リスト作成まで)


記事公開後の追記

まずこちら第2回記事で成果物を得てそれから下記の話を、の流れでもOKです。

初めに

みなさんすばらしいfind/47というサイトはご存知でしょうか。
全国47都道府県の美しい風景画像が高画質で提供されています。当方調査によると2020年10月末日現在で日本全国合計1080種類あり、(画像にもよりますが)これをS, M, L, XLの解像度別にダウンロードすることが可能です。しかしながら公開されているアクセス数をざっと見た限りでは、十分に認知や活用がされているとはいえないようで、惜しまれます。
そこでこの記事(全2回+)では、Pythonを使用して(1)画像のリストを作成し、(2)ダウンロードし、さらに(3)Ubuntu/LXDEでvarietyを使い壁紙のオートチェンジ環境を実現する方法を紹介します。
レベル的には、「スクレイピングで実現したいことがあって、Pythonで実装し、確認する」入門的なものです。私自身、さほどのテクニックや最適化は図っていません。あらかじめご了承ください。最終的には、下記の画像のようなデスクトップを(個人的な趣味で)実現して満足する。それだけです。

留意事項

今回の第1回の記事では実行に影響ありませんが、画像のダウンロードには約10GBの空き容量が必要になります。第2回か第3回の記事で解像度を下げて画像のダウンロード容量を減らす方法について触れる予定です。

動作環境

各自適宜適当にやってください。

Ubuntu

$ cat /etc/issue
Ubuntu 20.04.1 LTS \n \l

Python

$ python3 --version
Python 3.8.5

installed by pip3

他にもあるかもしれません。コード冒頭を見て対応してください。

$ pip3 list
beautifulsoup4               4.8.2
requests                     2.22.0
tqdm                         4.50.2

動作概要とコード

動作概要

画像を保存するまでは6工程を経ます。今回の1/2記事では、そのうち1-3までの工程を行います。
具体的には、ダウンロード対象画像のリストを、ある程度の可読性の高い形でテキストファイルに出力します。まずページ送りを行いながら網羅的に可読性の低い形で作り(stage 01, インメモリ)、リストの各行にサイズ情報を付加し(stage 02, インメモリ)、CSV形式で出力します(stage 03, ファイル出力)。
ここまでで、何地方(0-7)の何県(0-46)の何というファイル名(注:拡張子なし)の何サイズ(xl, x, m, s)を取得し、そして合計で何枚の画像を取得できるのがか確定します。

コード(1)01_generate_urls.py

適宜適当なフォルダ(例、/home/nekoneko/codes/python/find47)を作り、その直下に01_generate_urls.pyのファイル名で保存してください。

#!/usr/bin/env python3
# coding+ utf-8

import csv
import re
import requests
import subprocess
import time
from bs4 import BeautifulSoup

# e.g. https://search.find47.jp/ja/images?area=kinki&prefectures=kyoto&page=3

# declare variables

base_url            =   'https://search.find47.jp/ja/images?'

valid_urls          = []
target_urls         = []

areas               = [ 'hokkaido', 'tohoku', 'kanto-koshinetsu', 'tokai-hokuriku', 'kinki',
                        'chugoku' , 'sikoku', 'kyushu-okinawa' ]
prefs_head_by_area  = [  0,  1,  7, 17, 24, 30, 35, 39 ]
prefs_count_by_area = [  1,  6, 10,  7,  6,  5,  4,  8 ]
prefectures         = [
                        'hokkaido' ,

                        'aomori'   , 'iwate'     , 'miyagi'    , 'akita'    , 'yamagata' ,
                        'fukushima',

                        'tokyo'    , 'kanagawa'  , 'saitama'   , 'chiba'    , 'ibaraki'  ,
                        'tochigi'  , 'gunma'     , 'yamanashi' , 'niigata'  , 'nagano'   ,

                        'toyama'   , 'ishikawa'  , 'fukui'     , 'gifu'     , 'shizuoka' ,
                        'aichi'    , 'mie'       ,

                        'shiga'    , 'kyoto'     , 'osaka'     , 'hyogo'    , 'nara'     ,
                        'wakatama' ,

                        'tottori'  , 'shimane'   , 'okayama'   , 'hitoshima', 'yamaguchi',

                        'tokushima', 'kagawa'    , 'ehime'     , 'kochi'    ,

                        'fukuoka'  , 'saga'      , 'nagasaki'  , 'kumamoto' , 'oita'     ,
                        'miyazaki' , 'kagoshima' , 'okinawa'
                      ]

image_sizes         = ['xl' , 'l' , 'm' , 's']
max_pages           = 21
waiting_seconds     = 6

# make output folder

command = ('mkdir', '-p', './txt')
res     = subprocess.call(command)

# functions

def generate_target_urls():
    for  i in range(0,len(prefs_head_by_area)):

        for j in range(prefs_head_by_area[i], \
                       prefs_head_by_area[i] + prefs_count_by_area[i]):

            for k in range(1, max_pages):
                target_url = base_url \
                 + 'area=' + areas[i] \
                 + '&prefectures='\
                 + prefectures[j] \
                 + '&page=' \
                 + str(k)

                time.sleep(waiting_seconds)
                html          = requests.get(target_url)
                html.encoding = 'utf-8'
                soup          = BeautifulSoup(html.text, 'html.parser')
                atags         = soup.find_all('a')

                for l in atags:
                    m = l['href']
                    n = '^/ja/i/'
                    o = re.match( n, m )
                    if o:
                        target_urls.append([i, j, m, 'z'])
                    else:
                        None
    return

def update_details_in_target_urls():
    base_image_url = 'https://search.find47.jp/ja/images/'
    for i in target_urls:

        for j in image_sizes:
            time.sleep(waiting_seconds)
            image_url = base_image_url + str(i[2][-5:]) + '/download/' + j
            image_link = requests.get(image_url)

            if image_link.status_code == 200:
                target_urls[target_urls.index(i)][2] = str(i[2][-5:])
                target_urls[target_urls.index(i)][3] = j
                break
    return

def write_out_to_csv_file():
    with open('./txt/01.csv', mode = 'w', encoding = 'utf-8') as f:
        for i in target_urls:
            writer = csv.writer(f)
            writer.writerow(i)
    f.close()
    return

# main routine
## generate target urls list as a text file with info in a simple format.

### stage 01
print('stage 01/03 started.')
generate_target_urls()
print('stage 01 completed.')

### stage 02
print('stage 02/03 started.')
update_details_in_target_urls()
print('stage 02 completed.')

### stage 03
print('stage 03/03 started.')
write_out_to_csv_file()
print('stage 03/03 completed.')
print('All operations of 01_generate_urls.py completed.')

# end of this script

コード(2)

作成した適宜適当なフォルダ(例、/home/nekoneko/codes/python/find47)の直下に47_finder.shのファイル名で保存してください。さらに、chmod +xしてください。

#!/bin/bash
cd /home/nekoneko/codes/python/find47

python3 ./01_generate_urls.py > ./txt/01.log 2>&1
#python3 ./02_download_jpgs.py > ./txt/02.log 2>&1

実行

cronに入れるなどして行うのがお勧めです。
ログファイルは./txt/01.logです。
8地域(北海道から九州沖縄まで)、全1080画像であることが確認できます。

実行例

ちょっと違いますが下記のような形式でファイルが./txt/01.csvとして作られます(画面は開発中のものです)。

予想所要時間

今回のリスト作成に、10時間前後です。
次回の画像取得に、同様に10時間前後です。

今回のまとめ

すばらしいfind/47というサイトから、全国47都道府県の美しい風景画像をPythonを使って取得する手順を紹介する記事です。このうち、今回は対象URLをテキストファイルに出力するところまでをコード付きで説明しました。次回の記事では、今回得られたリストを元に、画像を取得します。