Qiitaのトレンド情報を保存しておく環境をDockerで作成しました。
基本的にコンテナを立ち上げていれば、毎日勝手にスクレイピング処理が走り、JSON化したトレンド情報を保存してくれます。この記事は以下のような方におすすめです。
- Qiitaのトレンドを分析しておきたいな
- Pythonの勉強を少しやってみたいな
- Dockerちょっと触ってみたい
※保存しておくQiitaのJSONフォーマットについて
- author(トレンド入りした著者の一覧
- list(トレンド記事の一覧
- tag(トレンドの記事に付けられたタグ一覧
実際に保存しておくJSONの中身は以下のようになっています。
author
: Qiitaにトレンド入りした著者を取得する
著者のユーザーネームを一覧化。
[
"uhyo",
"suin",
"Yz_4230",
"atskimura",
"pineappledreams",
"Amanokawa",
"k_shibusawa",
"minakawa-daiki",
"morry_48",
"c60evaporator",
"takuya_tsurumi",
"TomoEndo",
"yhatt",
"CEML",
"moritalous",
"svfreerider",
"daisukeoda",
"karaage0703",
"tommy19970714",
"tyru",
"galileo15640215",
"keitah",
"mocapapa",
"akeome",
"ssssssssok1",
"yuno_miyako",
"katzueno",
"cometscome_phys",
"mpyw",
"akane_kato"
]
list
: Qiitaにトレンド入りした記事の一覧を取得する
以下の情報を出力します。
- 記事のUUID(記事のID)
- 記事のタイトル
- 記事のURL
- 記事の著者名
- LGTM数
- 記事に付けたれたタグ, タグURL
[
{
"article_id":"e66cbca2f582e81d5b16",
"article_title":"Let's Encryptを使用しているウェブページをブロックするプロキシサーバー",
"article_url":"https://qiita.com/uhyo/items/e66cbca2f582e81d5b16",
"author_name":"uhyo",
"likes":66,
"tag_list":[
{
"tag_link":"/tags/javascript",
"tag_name":"JavaScript"
},
{
"tag_link":"/tags/node.js",
"tag_name":"Node.js"
},
{
"tag_link":"/tags/proxy",
"tag_name":"proxy"
},
{
"tag_link":"/tags/https",
"tag_name":"HTTPS"
},
{
"tag_link":"/tags/letsencrypt",
"tag_name":"letsencrypt"
}
]
},
{
"article_id":"83ebaf96caa2c13c8b2f",
"article_title":"macOSのスクリーンセーバーをHTML・CSS・JSで作る (Swiftスキル不要)",
"article_url":"https://qiita.com/suin/items/83ebaf96caa2c13c8b2f",
"author_name":"suin",
"likes":60,
"tag_list":[
{
"tag_link":"/tags/html",
"tag_name":"HTML"
},
{
"tag_link":"/tags/css",
"tag_name":"CSS"
},
{
"tag_link":"/tags/javascript",
"tag_name":"JavaScript"
},
{
"tag_link":"/tags/macos",
"tag_name":"macos"
}
]
}
]
Qiitaのトレンドは1日に2回、毎日5時/17時に更新更新されていますが、そこまで記事の入れ替わりはないので1日1回だけの実行にしておこうと思います。
tag
: Qiitaにトレンド入りした記事に付けられたタグを取得する
[
{
"tag_link":"/tags/python",
"tag_name":"Python"
},
{
"tag_link":"/tags/r",
"tag_name":"R"
},
{
"tag_link":"/tags/%e6%a9%9f%e6%a2%b0%e5%ad%a6%e7%bf%92",
"tag_name":"機械学習"
}
]
上記の記事の一覧でもタグは取得していますが、一個の記事に紐付けられたタグなので、異なる記事で同じタグが付いていた場合重複してしまいます。そのため、重複したタグを省いてトレンド入りしたタグだけ一覧で保存しておく処理にしました。
DockerでPythonが実行できる環境を作成しよう
簡易的なDocker環境を作成していきます。
ディレクトリ構成は以下のような感じ。
├── batch
│ └── py
│ └── article.py
├── docker
│ └── python
│ ├── Dockerfile
│ ├── etc
│ │ └── cron.d
│ │ └── qiita
│ └── requirements.txt
├── docker-compose.yml
└── mnt
└── json
├── author
├── list
└── tag
-
batch
directory
pythonファイルを置いています。
このファイルがスクレイピングを行う実ファイルです。 -
docker
directory
コンテナの内部で必要なものだったり、実際のcron設定はここで置いています -
mnt
directory
host上のディレクトリをマウントしていて、ここにスクレイピングの結果JSONファイルが生成されます
Qiitaのトレンドをスクレイピングで取得しよう (batch directory
)
batchディレクトリの中にある実ファイルarticle.py
の中身です。
過去にこんな記事を書いていたので、詳しいやり方とかはそっちで解説しています。
≫ Qiitaのトレンド(ランキング)を取得してSlackに送信する
この記事ではあくまでプログラムだけにします。
上記の記事のプログラムとの相違点は以下の2点です。
記事の一覧だけ欲しいねん!って人は上記の記事だけで事足りると思います。
- トレンド入りした記事のタグと著者を取得
- 取得した内容をJSON化して保存しておく
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
from bs4 import BeautifulSoup
import json
import datetime
import os
def get_article_tags(detail_url):
tag_list = []
res = requests.get(detail_url, headers=headers)
# htmlをBeautifulSoupで扱う
soup = BeautifulSoup(res.text, "html.parser")
tags = soup.find_all(class_="it-Tags_item")
for tag in tags:
tag_name = tag.get_text()
tag_link = tag.get('href')
tag_list.append({
'tag_name' : tag_name,
'tag_link': tag_link
})
return tag_list
def write_json(json_list, path):
with open(path, 'w') as f:
f.write(json.dumps(json_list, ensure_ascii=False, indent=4, sort_keys=True, separators=(',', ':')))
def mkdir(path):
os.makedirs(path, exist_ok=True)
def get_unique_list(seq):
seen = []
return [x for x in seq if x not in seen and not seen.append(x)]
def get_unique_tag(tag_lists):
tags = []
for v in tag_lists:
for i in v:
tags.append(i)
return tags
try:
# Root URL
url = "https://qiita.com/"
headers = {
"User-Agent" : "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1"
}
today_date = datetime.datetime.now().date()
items = []
item_json = []
result = []
res = requests.get(url, headers=headers)
# htmlをBeautifulSoupで扱う
soup = BeautifulSoup(res.text, "html.parser")
try:
main_items = soup.find(class_="p-home_main")
for main_items in soup.find_all():
if "data-hyperapp-props" in main_items.attrs:
item_json.append(main_items["data-hyperapp-props"])
items = json.loads(item_json[1])
except:
raise Exception("Not Found Json Dom Info")
if 'edges' not in items['trend']:
raise Exception("The expected list does not exist")
try:
item_detail_list = []
tags_list = []
author_list = []
for edges in items['trend']['edges']:
uuid = edges['node']['uuid']
title = edges['node']['title']
likes = edges['node']['likesCount']
article_url = url + edges['node']['author']['urlName'] + '/items/' + uuid
author_name = edges['node']['author']['urlName']
create_at = datetime.datetime.now().date()
tag_list = get_article_tags(article_url)
item = {
'article_title' : title,
'article_url' : article_url,
'article_id' : edges['node']['uuid'],
'likes' : likes,
'uuid' : uuid,
'author_name' : author_name,
'tag_list' : tag_list,
}
item_detail_list.append(item)
tags_list.append(tag_list)
author_list.append(author_name)
mkdir('/mnt/json/list/')
mkdir('/mnt/json/tag/')
mkdir('/mnt/json/author/')
# タグをuniqu化
tags_list = get_unique_tag(tags_list)
# jsonファイルを書き出し
write_json(item_detail_list, f"/mnt/json/list/{today_date}.json")
write_json(tags_list, f"/mnt/json/tag/{today_date}.json")
write_json(author_list, f"/mnt/json/author/{today_date}.json")
except:
raise Exception("Can't Create Json")
except Exception as e:
# jsonファイル作成失敗
mkdir('/mnt/log/')
with open(f'/mnt/log/{today_date}', 'w') as f:
f.write(e)
次に上記のファイルを実行する環境を作成します。
Pythonを動かすためのDocker部分を作成(docker directory
)
docker-compose.ymlの作成
ここは大したことしてない。
volumesでPC上のディレクトリとマウント。
version: "3"
qiita_batch:
container_name: "qiita_batch"
build:
context: ./docker/python
tty: true
volumes:
- ./batch:/usr/src/app
- ./mnt:/mnt
Dockerfileの作成
Dockerfile汚いのは許して...軽く説明だけ↓
- コンテナ内のタイムゾーンの設定(cron設定のため
- cronを反映
- requirement.txtで必要なモジュールをインストール
cronをちゃんと指定した日本時間で実行したいのであれば、タイムゾーンの設定は必須ですね。
なんかごちゃごちゃやって、ようやく日本時間に変えられたんですが、もっとうまいやり方あるはず...。
cronの設定はetc/cron.d/qiitaにまとめておいて、後々crontabに書き込むような処理にしてます。管理が楽になるのでこっちの方がいいかなと。間違ってもcrontab -r
というコマンドは呼び出してはいけない...!!
FROM python:3
ARG project_dir=/usr/src/app
WORKDIR $project_dir
ADD requirements.txt $project_dir/py/
ADD /etc/cron.d/qiita /etc/cron.d/
ENV TZ=Asia/Tokyo
RUN apt-get update && \
apt-get install -y cron less vim tzdata && \
rm -rf /var/lib/apt/lists/* && \
echo "${TZ}" > /etc/timezone && \
rm /etc/localtime && \
ln -s /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
dpkg-reconfigure -f noninteractive tzdata && \
chmod 0744 /etc/cron.d/* && \
touch /var/log/cron.log && \
crontab /etc/cron.d/qiita && \
pip install --upgrade pip && \
pip install -r $project_dir/py/requirements.txt
CMD ["cron", "-f"]
Pythonの実行に必要なパッケージをまとめたrequirement.txtを作成
requirement.txtは自分のMacbookProで使用していたものを出力しただけなので、かなり適当に色々なものが入っています。いらないものは適宜削ってくださいまし。beautifulsoup4
とrequests
とjson
だけあれば事足ります。なんか足らん!って人は動かしながら足らないやつpip install!!
appdirs==1.4.3
beautifulsoup4==4.8.1
bs4==0.0.1
certifi==2019.9.11
chardet==3.0.4
Click==7.0
filelock==3.0.12
get==2019.4.13
gunicorn==20.0.4
idna==2.8
importlib-metadata==1.5.0
importlib-resources==1.0.2
itsdangerous==1.1.0
Jinja2==2.11.1
MarkupSafe==1.1.1
post==2019.4.13
public==2019.4.13
query-string==2019.4.13
request==2019.4.13
requests==2.22.0
six==1.14.0
soupsieve==1.9.5
urllib3==1.25.7
virtualenv==20.0.1
Werkzeug==1.0.0
zipp==2.2.0
cron設定
/etc/cron.d/qiita
の中身です
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
LANG=ja_JP.UTF-8
# Create Qiita JSON (every day AM:10:00)
0 10 * * * python /usr/src/app/py/article.py >> /var/log/cron.log 2>&1
こんな感じ!
あとはdocker-compose up -d
でやれば立ち上がるので、放置しておけば勝手にQiitaにスクレイピングしに行ってjsonファイルを作成してくれます。簡易的なDocker環境で出来るのでおすすめ!