🍺

Google Colabを使ってスクレイピングをしてみた話

20 min read

きっかけ

ちょっとスクレイピングしたいなーと思っていた時に、たまたまChromeのDiscoverで以下のリンクが紹介?されていました。

https://logmi.jp/tech/articles/325339

Published at 2021-10-29 06:30となっているけど、2021年08月17日に開催とも書いていて
二か月も寝かしてたのねと思いつつも
Google Colaboratory
未経験なので使ってみることにしました。

環境準備

Googleアカウントは普段から使っているものを使用しました。

とりあえず

  • ファイルから「ノートブックを新規作成」
  • 左下のコマンドパレットから「ドライブをマウント」
    • 「/mnt」ではなく「/content/drive」にマウントされるんですね
  • 一旦スクレイピングが目的なのでランタイムはそのまま使用
!pwd

の結果は
「/content」
でした

!ls -la

の結果は

total 20
drwxr-xr-x 1 root root 4096 Oct 30 18:23 .
drwxr-xr-x 1 root root 4096 Oct 30 18:19 ..
drwxr-xr-x 4 root root 4096 Oct 26 13:33 .config
drwx------ 6 root root 4096 Oct 30 18:23 drive
drwxr-xr-x 1 root root 4096 Oct 26 13:34 sample_data

でした。
左のメニューから見えてるファイルと比べると「.config」というのが隠れていたことが分かります。
名前的にnotebooksの設定でしょうか?

!ls -la .config .config/configurations
.config:
total 36
drwxr-xr-x 4 root root 4096 Oct 26 13:33 .
drwxr-xr-x 1 root root 4096 Oct 30 18:23 ..
-rw-r--r-- 1 root root    7 Oct 26 13:33 active_config
-rw-r--r-- 1 root root    0 Oct 26 13:33 config_sentinel
drwxr-xr-x 2 root root 4096 Oct 26 13:33 configurations
-rw------- 1 root root    5 Jan  1  2040 gce
-rw-r--r-- 1 root root    3 Oct 26 13:32 .last_opt_in_prompt.yaml
-rw-r--r-- 1 root root   37 Oct 26 13:33 .last_survey_prompt.yaml
-rw-r--r-- 1 root root  135 Oct 26 13:33 .last_update_check.json
drwxr-xr-x 3 root root 4096 Oct 26 13:32 logs

.config/configurations:
total 12
drwxr-xr-x 2 root root 4096 Oct 26 13:33 .
drwxr-xr-x 4 root root 4096 Oct 26 13:33 ..
-rw-r--r-- 1 root root   94 Oct 26 13:33 config_default

あとはたった3行でVSCode(codeserver)をGoogle Colabで使う方法に従って
colabcodeのインストールと実行

!pip install colabcode
from colabcode import ColabCode
ColabCode()

何かしらエラーが出ているようにも見えましたが、
nglok.ioのURLをクリックするだけでcode-serverが実行出来ました。

?folder=/workspace

のようにfolder=pathの形でクエリを追加するとそのフォルダでワークスペースを開けるみたいです。

code-server側が背景白でいつもと見た目が違うので「Dark+」に変更しました。

ここで設定を変更した項目はvscodeだとsetting.jsonに保存されるのですが、

/root/.local/share/code-server/User/setting.json

にありました!

{
    "workbench.colorTheme": "Default Dark+"
}

ドライブに保存してリンクを張ることにします。(code-server側のターミナルで実行しました)

mkdir '/content/drive/MyDrive/Colab Notebooks/vscode'
cd '/content/drive/MyDrive/Colab Notebooks/vscode'
cp '/root/.local/share/code-server/User/setting.json' './'
rm '/root/.local/share/code-server/User/setting.json'
ln -s '/content/drive/MyDrive/Colab Notebooks/vscode/settings.json' '/root/.local/share/code-server/User/settings.json'

この作業をしている時に時刻が日本標準時になっていない事に気づきました。(code-server側のターミナルで実行しました。)

rm /etc/localtime
ln -s /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

時刻が日本標準時になりました。

ExtensionがWarningが出ていて画面から追加できませんでした。code-serverの制限らしいです。
vscode同様コマンドラインからの追加は行えるようなので、(code-serverで動作可能かどうかは別として)必要に応じて追加はできそうです。

code-server --install-extension formulahendry.code-runner

スクレイピングの実行

環境設定はほどほどにして、スクレイピングを始めます。
気象庁 東京 1922年10月
の気温を抜いてみます。

import requests
from bs4 import BeautifulSoup

url = 'https://www.data.jma.go.jp/obd/stats/etrn/view/daily_s1.php?prec_no=44&block_no=47662&year=1922&month=10&day=&view='
response = requests.get(url)

data = BeautifulSoup(response.text, 'html.parser')

print(data.title)
print(data.title.text)

tr_list = data.find_all('tr', style='text-align:right;')
for tr_record in tr_list:
    td_list = tr_record.find_all('td')
    print(td_list[0].find('a').text)
    print(td_list[6].text)
    print(td_list[7].text)
    print(td_list[8].text)
実行結果
<title>æ°è±¡åºï½éå»ã®æ°è±¡ãã¼ã¿æ¤ç´¢</title>
æ°è±¡åºï½éå»ã®æ°è±¡ãã¼ã¿æ¤ç´¢
1
19.3
24.6
14.2
2
23.0
28.6
16.5
3
16.7
21.5
15.1
4
16.4
18.9
14.2
5
17.8
23.2
14.2
6
15.6
18.6
13.1
7
16.9
19.2
13.6
8
20.8
27.7
17.4
9
20.4
26.5
14.8
10
20.2
27.0
15.4
11
15.9
22.5
11.4
12
15.0
21.5
8.6
13
16.1
22.9
12.2
14
16.4
23.3
10.9
15
17.7
23.6
14.6
16
16.9
21.7
13.7
17
17.6
21.7
14.4
18
14.7
15.9
13.3
19
16.8
23.0
10.6
20
15.3
20.1
11.7
21
14.0
20.0
9.9
22
13.7
21.2
8.8
23
14.4
22.1
8.8
24
15.0
22.8
8.7
25
15.6
19.6
11.7
26
19.4
23.9
15.3
27
18.1
26.7
12.6
28
14.9
21.7
10.5
29
14.4
22.1
8.8
30
15.2
22.1
8.4
31
16.0
20.4
11.9

おそらく日本語が文字化けてます。
おそらくターミナルの設定の問題だと思っていますが、タイトルを抜くのは趣旨では無いためここでは原因追及はせず、一旦無視して進めます。

SQLAlchemyでスクレイピングしたデータの受け先を作成

ORマッパーを使ってみたいので、ググって上位に多かったSQLAlchemyを使ってみます。
とりあえずバージョンチェック。

import sqlalchemy
print(sqlalchemy.__version__)

実行結果

1.4.25

インストールされているようです。1.4でした。

スクレイピングしたデータを保存する処理の準備をします。
今回はsqliteで保存します。

一旦以下のような構成としました。

erDiagram

pref ||--o{ spot : pref_id
spot ||--o{ temperature : spot_id

pref {
    id integer
    name string
    prec_no integer
}

spot {
    id integer
    name string
    pref_id integer
    block_no integer
    longitude float
    latitude float
}

temperature {
    spot_id integer
    year integer
    month integer
    day integer
    adt float
    hdt float
    cdt float
}

最終的にはドライブに作成しますが、一旦オンメモリで動作させてみます。

from sqlalchemy import create_engine, Table, Column, Integer, Float, String, ForeignKey
from sqlalchemy.orm import Session, relationship, registry

db_url = "sqlite+pysqlite:///:memory:"

engine = create_engine(db_url, echo=True, future=True)


mapper_registry = registry()
AlchemyBase = mapper_registry.generate_base()


class Pref(AlchemyBase):
    __tablename__ = 'pref'

    id = Column(Integer, primary_key=True)
    name = Column(String(64), nullable=False)
    prec_no = Column(Integer, nullable=False)

    def __repr__(self):
        return f"Pref(id={self.id!r}, name={self.name!r}, prec_no={self.prec_no!r}"


class Spot(AlchemyBase):
    __tablename__ = 'spot'

    id = Column(Integer, primary_key=True)
    name = Column(String(64), nullable=False)
    pref_id = Column(Pref.id.type, ForeignKey(Pref.id), nullable=False)
    block_no = Column(Integer, nullable=False)

    longitude = Column(Float)
    latitude = Column(Float)

    pref = relationship("Pref")

    def __repr__(self):
        return f"Spot(id={self.id!r}, name={self.pref.name!r}/{self.name!r}, " + \
            f"prec_no={self.pref.prec_no!r}, block_no={self.block_no!r}" + \
            f"longitude={self.longitude!r}, latitude={self.latitude!r}"


class Temperature(AlchemyBase):
    __tablename__ = 'temperature'

    spot_id = Column(Spot.id.type, ForeignKey(Spot.id), primary_key=True)
    year = Column(Integer, primary_key=True)
    month = Column(Integer, primary_key=True)
    day = Column(Integer, primary_key=True)
    # average daily temperature
    adt = Column(Float)
    # hottest daily temperature
    hdt = Column(Float)
    # coldest daily temperature
    cdt = Column(Float)

    spot = relationship("Spot")

    def __repr__(self):
        return f"Temperature(spot={self.spot.name!r}, year={self.year!r}, month={self.month!r}," + \
            f"day={self.day!r}, adt={self.adt!r}, hdt={self.hdt!r}, cdt={self.cdt!r})"


pref_tyo = Pref(name="東京", prec_no=44)
spot_tyo = Spot(name="東京", pref=pref_tyo, block_no=47662)
d19221001 = Temperature(spot=spot_tyo, year=1922, month=10,
                        day=1, adt=19.3, hdt=24.6, cdt=14.2)

mapper_registry.metadata.create_all(engine)


with Session(engine) as session:
    session.add(pref_tyo)
    session.add(spot_tyo)
    session.add(d19221001)
    session.commit()

実行結果
2021-11-03 09:00:30,666 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2021-11-03 09:00:30,667 INFO sqlalchemy.engine.Engine PRAGMA main.table_info("pref")
2021-11-03 09:00:30,667 INFO sqlalchemy.engine.Engine [raw sql] ()
2021-11-03 09:00:30,668 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info("pref")
2021-11-03 09:00:30,668 INFO sqlalchemy.engine.Engine [raw sql] ()
2021-11-03 09:00:30,669 INFO sqlalchemy.engine.Engine PRAGMA main.table_info("spot")
2021-11-03 09:00:30,669 INFO sqlalchemy.engine.Engine [raw sql] ()
2021-11-03 09:00:30,670 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info("spot")
2021-11-03 09:00:30,670 INFO sqlalchemy.engine.Engine [raw sql] ()
2021-11-03 09:00:30,670 INFO sqlalchemy.engine.Engine PRAGMA main.table_info("temperature")
2021-11-03 09:00:30,671 INFO sqlalchemy.engine.Engine [raw sql] ()
2021-11-03 09:00:30,671 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info("temperature")
2021-11-03 09:00:30,671 INFO sqlalchemy.engine.Engine [raw sql] ()
2021-11-03 09:00:30,673 INFO sqlalchemy.engine.Engine 
CREATE TABLE pref (
        id INTEGER NOT NULL, 
        name VARCHAR(64) NOT NULL, 
        prec_no INTEGER NOT NULL, 
        PRIMARY KEY (id)
)


2021-11-03 09:00:30,673 INFO sqlalchemy.engine.Engine [no key 0.00034s] ()
2021-11-03 09:00:30,674 INFO sqlalchemy.engine.Engine 
CREATE TABLE spot (
        id INTEGER NOT NULL, 
        name VARCHAR(64) NOT NULL, 
        pref_id INTEGER NOT NULL, 
        block_no INTEGER NOT NULL, 
        longitude FLOAT, 
        latitude FLOAT, 
        PRIMARY KEY (id), 
        FOREIGN KEY(pref_id) REFERENCES pref (id)
)


2021-11-03 09:00:30,675 INFO sqlalchemy.engine.Engine [no key 0.00031s] ()
2021-11-03 09:00:30,676 INFO sqlalchemy.engine.Engine 
CREATE TABLE temperature (
        spot_id INTEGER NOT NULL, 
        year INTEGER NOT NULL, 
        month INTEGER NOT NULL, 
        day INTEGER NOT NULL, 
        adt FLOAT, 
        hdt FLOAT, 
        cdt FLOAT, 
        PRIMARY KEY (spot_id, year, month, day), 
        FOREIGN KEY(spot_id) REFERENCES spot (id)
)


2021-11-03 09:00:30,676 INFO sqlalchemy.engine.Engine [no key 0.00042s] ()
2021-11-03 09:00:30,677 INFO sqlalchemy.engine.Engine COMMIT
2021-11-03 09:00:30,679 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2021-11-03 09:00:30,681 INFO sqlalchemy.engine.Engine INSERT INTO pref (name, prec_no) VALUES (?, ?)
2021-11-03 09:00:30,681 INFO sqlalchemy.engine.Engine [generated in 0.00046s] ('東京', 44)
2021-11-03 09:00:30,684 INFO sqlalchemy.engine.Engine INSERT INTO spot (name, pref_id, block_no, longitude, latitude) VALUES (?, ?, ?, ?, ?)
2021-11-03 09:00:30,684 INFO sqlalchemy.engine.Engine [generated in 0.00042s] ('東京', 1, 47662, None, None)
2021-11-03 09:00:30,687 INFO sqlalchemy.engine.Engine INSERT INTO temperature (spot_id, year, month, day, adt, hdt, cdt) VALUES (?, ?, ?, ?, ?, ?, ?)
2021-11-03 09:00:30,687 INFO sqlalchemy.engine.Engine [generated in 0.00049s] (1, 1922, 10, 1, 19.3, 24.6, 14.2)
2021-11-03 09:00:30,689 INFO sqlalchemy.engine.Engine COMMIT

問題なさそうなので、合わせます。
保存先もドライブにします。

from bs4 import BeautifulSoup
import requests
from sqlalchemy import create_engine, Table, Column, Integer, Float, String, ForeignKey
from sqlalchemy.orm import Session, relationship, registry

db_url = "sqlite+pysqlite:////content/drive/MyDrive/Colab Notebooks/sqlite3/temperature.db"

engine = create_engine(db_url, echo=True, future=True)


mapper_registry = registry()
AlchemyBase = mapper_registry.generate_base()


class Pref(AlchemyBase):
    __tablename__ = 'pref'

    id = Column(Integer, primary_key=True)
    name = Column(String(64), nullable=False)
    prec_no = Column(Integer, nullable=False)

    def __repr__(self):
        return f"Pref(id={self.id!r}, name={self.name!r}, prec_no={self.prec_no!r}"


class Spot(AlchemyBase):
    __tablename__ = 'spot'

    id = Column(Integer, primary_key=True)
    name = Column(String(64), nullable=False)
    pref_id = Column(Pref.id.type, ForeignKey(Pref.id), nullable=False)
    block_no = Column(Integer, nullable=False)

    longitude = Column(Float)
    latitude = Column(Float)

    pref = relationship("Pref")

    def __repr__(self):
        return f"Spot(id={self.id!r}, name={self.pref.name!r}/{self.name!r}, " + \
            f"prec_no={self.pref.prec_no!r}, block_no={self.block_no!r}" + \
            f"longitude={self.longitude!r}, latitude={self.latitude!r}"


class Temperature(AlchemyBase):
    __tablename__ = 'temperature'

    spot_id = Column(Spot.id.type, ForeignKey(Spot.id), primary_key=True)
    year = Column(Integer, primary_key=True)
    month = Column(Integer, primary_key=True)
    day = Column(Integer, primary_key=True)
    # average daily temperature
    adt = Column(Float)
    # hottest daily temperature
    hdt = Column(Float)
    # coldest daily temperature
    cdt = Column(Float)

    spot = relationship("Spot")

    def __repr__(self):
        return f"Temperature(spot={self.spot.name!r}, year={self.year!r}, month={self.month!r}," + \
            f"day={self.day!r}, adt={self.adt!r}, hdt={self.hdt!r}, cdt={self.cdt!r})"


mapper_registry.metadata.create_all(engine)


pref_tyo = Pref(name="東京", prec_no=44)
spot_tyo = Spot(name="東京", pref=pref_tyo, block_no=47662)


url = 'https://www.data.jma.go.jp/obd/stats/etrn/view/daily_s1.php?prec_no=44&block_no=47662&year=1922&month=10&day=&view='
response = requests.get(url)

data = BeautifulSoup(response.text, 'html.parser')

with Session(engine) as session:
    session.add(pref_tyo)
    session.add(spot_tyo)
    tr_list = data.find_all('tr', style='text-align:right;')
    for tr_record in tr_list:
        td_list = tr_record.find_all('td')
        session.add(Temperature(spot=spot_tyo, year=1922, month=10,
                                day=td_list[0].find('a').text, adt=td_list[6].text, hdt=td_list[7].text, cdt=td_list[8].text))
    session.commit()
実行結果
2021-11-03 09:38:32,160 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2021-11-03 09:38:32,161 INFO sqlalchemy.engine.Engine PRAGMA main.table_info("pref")
2021-11-03 09:38:32,161 INFO sqlalchemy.engine.Engine [raw sql] ()
2021-11-03 09:38:32,162 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info("pref")
2021-11-03 09:38:32,162 INFO sqlalchemy.engine.Engine [raw sql] ()
2021-11-03 09:38:32,163 INFO sqlalchemy.engine.Engine PRAGMA main.table_info("spot")
2021-11-03 09:38:32,163 INFO sqlalchemy.engine.Engine [raw sql] ()
2021-11-03 09:38:32,163 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info("spot")
2021-11-03 09:38:32,163 INFO sqlalchemy.engine.Engine [raw sql] ()
2021-11-03 09:38:32,164 INFO sqlalchemy.engine.Engine PRAGMA main.table_info("temperature")
2021-11-03 09:38:32,164 INFO sqlalchemy.engine.Engine [raw sql] ()
2021-11-03 09:38:32,164 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info("temperature")
2021-11-03 09:38:32,164 INFO sqlalchemy.engine.Engine [raw sql] ()
2021-11-03 09:38:32,165 INFO sqlalchemy.engine.Engine 
CREATE TABLE pref (
        id INTEGER NOT NULL, 
        name VARCHAR(64) NOT NULL, 
        prec_no INTEGER NOT NULL, 
        PRIMARY KEY (id)
)


2021-11-03 09:38:32,165 INFO sqlalchemy.engine.Engine [no key 0.00012s] ()
2021-11-03 09:38:32,181 INFO sqlalchemy.engine.Engine 
CREATE TABLE spot (
        id INTEGER NOT NULL, 
        name VARCHAR(64) NOT NULL, 
        pref_id INTEGER NOT NULL, 
        block_no INTEGER NOT NULL, 
        longitude FLOAT, 
        latitude FLOAT, 
        PRIMARY KEY (id), 
        FOREIGN KEY(pref_id) REFERENCES pref (id)
)


2021-11-03 09:38:32,181 INFO sqlalchemy.engine.Engine [no key 0.00020s] ()
2021-11-03 09:38:32,192 INFO sqlalchemy.engine.Engine 
CREATE TABLE temperature (
        spot_id INTEGER NOT NULL, 
        year INTEGER NOT NULL, 
        month INTEGER NOT NULL, 
        day INTEGER NOT NULL, 
        adt FLOAT, 
        hdt FLOAT, 
        cdt FLOAT, 
        PRIMARY KEY (spot_id, year, month, day), 
        FOREIGN KEY(spot_id) REFERENCES spot (id)
)


2021-11-03 09:38:32,193 INFO sqlalchemy.engine.Engine [no key 0.00020s] ()
2021-11-03 09:38:32,203 INFO sqlalchemy.engine.Engine COMMIT
2021-11-03 09:38:32,437 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2021-11-03 09:38:32,440 INFO sqlalchemy.engine.Engine INSERT INTO pref (name, prec_no) VALUES (?, ?)
2021-11-03 09:38:32,441 INFO sqlalchemy.engine.Engine [generated in 0.00085s] ('東京', 44)
2021-11-03 09:38:32,449 INFO sqlalchemy.engine.Engine INSERT INTO spot (name, pref_id, block_no, longitude, latitude) VALUES (?, ?, ?, ?, ?)
2021-11-03 09:38:32,449 INFO sqlalchemy.engine.Engine [generated in 0.00088s] ('東京', 1, 47662, None, None)
2021-11-03 09:38:32,456 INFO sqlalchemy.engine.Engine INSERT INTO temperature (spot_id, year, month, day, adt, hdt, cdt) VALUES (?, ?, ?, ?, ?, ?, ?)
2021-11-03 09:38:32,457 INFO sqlalchemy.engine.Engine [generated in 0.00163s] ((1, 1922, 10, '1', 19.3, 24.6, 14.2), (1, 1922, 10, '2', 23.0, 28.6, 16.5), (1, 1922, 10, '3', 16.7, 21.5, 15.1), (1, 1922, 10, '4', 16.4, 18.9, 14.2), (1, 1922, 10, '5', 17.8, 23.2, 14.2), (1, 1922, 10, '6', 15.6, 18.6, 13.1), (1, 1922, 10, '7', 16.9, 19.2, 13.6), (1, 1922, 10, '8', 20.8, 27.7, 17.4)  ... displaying 10 of 31 total bound parameter sets ...  (1, 1922, 10, '30', 15.2, 22.1, 8.4), (1, 1922, 10, '31', 16.0, 20.4, 11.9))
2021-11-03 09:38:32,460 INFO sqlalchemy.engine.Engine COMMIT

できたー!
とりあえず正常に動作したように見えます。

スクレイピングしたデータの確認

sqliteのコマンドで確認してみます。
なさそうなのでインストールします。

apt-get install -y sqlite3
/content/drive/MyDrive/Colab Notebooks/sqlite3# ls -l
total 20
-rw------- 1 root root 20480 Nov  3 09:38 temperature.db

ファイルが作成されていました。ファイルサイズは20480になってます。

sqlite3 temperature.db
SQLite version 3.22.0 2018-01-22 18:45:57
Enter ".help" for usage hints.
sqlite> .tables
pref         spot         temperature
sqlite> select * from temperature;
1|1922|10|1|19.3|24.6|14.2
1|1922|10|2|23.0|28.6|16.5
1|1922|10|3|16.7|21.5|15.1
1|1922|10|4|16.4|18.9|14.2
1|1922|10|5|17.8|23.2|14.2
1|1922|10|6|15.6|18.6|13.1
1|1922|10|7|16.9|19.2|13.6
1|1922|10|8|20.8|27.7|17.4
1|1922|10|9|20.4|26.5|14.8
1|1922|10|10|20.2|27.0|15.4
1|1922|10|11|15.9|22.5|11.4
1|1922|10|12|15.0|21.5|8.6
1|1922|10|13|16.1|22.9|12.2
1|1922|10|14|16.4|23.3|10.9
1|1922|10|15|17.7|23.6|14.6
1|1922|10|16|16.9|21.7|13.7
1|1922|10|17|17.6|21.7|14.4
1|1922|10|18|14.7|15.9|13.3
1|1922|10|19|16.8|23.0|10.6
1|1922|10|20|15.3|20.1|11.7
1|1922|10|21|14.0|20.0|9.9
1|1922|10|22|13.7|21.2|8.8
1|1922|10|23|14.4|22.1|8.8
1|1922|10|24|15.0|22.8|8.7
1|1922|10|25|15.6|19.6|11.7
1|1922|10|26|19.4|23.9|15.3
1|1922|10|27|18.1|26.7|12.6
1|1922|10|28|14.9|21.7|10.5
1|1922|10|29|14.4|22.1|8.8
1|1922|10|30|15.2|22.1|8.4
1|1922|10|31|16.0|20.4|11.9

スクレイピングした値が格納できていました!

まとめ

お疲れさまでした。

Discussion

ログインするとコメントできます