プログラミング素人のはてなブログ

技術屋の末端。プログラミングも電気回路も専門外です。 コードに間違いなど見つけられたら、気軽にコメントください。 VC#、python3、ラズパイ始めました。

このブログを読んでいる人のブログを読んでいる人の…をpythonで取得してみた

このブログはプログラミングと電子工作に偏っているので、このブログを読んでいる人は、これらに興味があると考えられます。
では、その人が書いているブログもプログラミングや電子工作に関係するのではないか?と考えてpythonとbeautifulsoupで取得することを考えました。

ブログの購読者は↓から取得できます。
s51517765.hatenadiary.jp
f:id:s51517765:20190102141259p:plain

import requests
from bs4 import BeautifulSoup

def get_aboutpage_2_subscriber(url, id_list, profile_page_list):
    html = requests.get(url)
    soup = BeautifulSoup(html.text, 'lxml')  ##lxmlを指定するほうがよい
    alink = soup.find_all('a')

    for a in alink:
        if a.get("href") != None:
            link = a.get("href")
            if "https://blog.hatena.ne.jp/" in link and "abuse_report" not in link:  # https://blog.hatena.ne.jp/-/abuse_report?target_url=https%3A%2F%2Fs51517765.hatenadiary.jp%2Fabout
                link2 = link.replace("https://blog.hatena.ne.jp/", "")
                finded_id = link2.replace("/", "")
                if finded_id not in id_list:
                    id_list[finded_id] = 1
                    profile_page_list[finded_id] = link
                else:
                    id_list[finded_id] += 1
    return profile_page_list, id_list

beautifulsoupでalink = soup.find_all('a')のようにLinkを取得し"https://blog.hatena.ne.jp/"が含まれているものが、読者のブログページです。ひとつだけ、https://blog.hatena.ne.jp/-/abuse_report?target_url=https%3A%2F%2Fs51517765.hatenadiary.jp%2Faboutが余計なので、除外します。
ここから、読者のidを取得し、辞書に加えていきます。

次に、読者のidからプロフィールページを取得します。
プロフィールページはLinkのうち/aboutが含まれているものです。

import requests
from bs4 import BeautifulSoup
import time

def get_subscriber_aboutpage(id):
    url = "https://blog.hatena.ne.jp/" + id + "/"
    html = requests.get(url)
    soup = BeautifulSoup(html.text, 'lxml')  ##lxmlを指定するほうがよい
    alink = soup.find_all('a')

    for a in alink:
        if a.get("href") != None:
            link = a.get("href")
            if "/about" in link:
                return link
                break
    time.sleep(0.2)

これを繰り返し、幅優先探索の要領で、このブログの読者と読者のブログの読者を取得します。
f:id:s51517765:20190102143756p:plain
幅優先探索 - Wikipedia

idの辞書をforで回しているときに、辞書を書き換えようとすると、RuntimeError: dictionary changed size during iterationというエラーが出るので、一旦読み取り用の辞書を id_list_tmp = id_list.copy()として用意します。 id_list_tmp = id_listとするとpythonでは同じポインタにアクセス(参照渡し)するようでダメです。

if __name__ == '__main__':
    id_list = {}
    id_list_tmp = {}  # RuntimeError: dictionary changed size during iteration
    about_page_list = {}
    my_url = "https://s51517765.hatenadiary.jp/about"
    about_page_list, id_list = get_aboutpage_2_subscriber(my_url, id_list, about_page_list)
    i=0
    while i<2:
        i+=1
        id_list_tmp = id_list.copy() #辞書はループを回している間は書き換えできないので
        for id in id_list_tmp:
            print(id)
            try:
                profile_link = get_subscriber_aboutpage(id)
                get_aboutpage_2_subscriber(profile_link, id_list, about_page_list)
            except Exception as e:
                print("-------------------------------")
                print(e)
                print(profile_link)
                print("-------------------------------")
        for k, v in sorted(id_list.items(), key=lambda x: -x[1]):  # 降順ソート
            if v != 1:
                print(str(k) + ": " + str(v))
        # print(about_page_list)
        print(id_list)

とりあえず、3階まで取得してみましたが予想とは異なる結果でした。
ブログ読者であっても、自身はブログを書いていなかったりします。
また、この集計方法では重複カウントも出てきますが、正規化されないgoogleページランクのようなものでこれはこれでいいのかとも思います。
3階の時点で8000人近く出てきました。(私の読者の読者の読者)

3階までの結果の一部

ponyoponyokun: 79
dmasaki: 62
aka12aya70y: 62
ironchocolate: 62
riyunion2: 55
akira2013web: 49
sakuyaoi: 49
grazieatutti: 47
youhp01: 47
nue0801: 45
doubleworkandstock: 44
opio8: 43
oshaberiitboy: 42
torus1: 41
sayakasumi382: 41
fukai19930806347: 40
mksurf: 38
at25250410: 38
kihappy1: 38
hidamaru: 37
cat-whisker: 35
AKI1200: 35
brachiodesign: 35
a24o92: 35
bunntinnmalu: 35
kihaseason2015: 34
momokuri777: 34
axia18: 33
noritoikioi: 33
je_online: 33
mraka2015: 33
m07v-sk160: 33
sakabesharoushi: 32
editman: 31
koppamizin007: 30
brd1267: 29
kojiebi-gm: 29
umauma-free: 29
kotokunohate: 29
periaki0813: 28
syuusakukakizoe: 28
takipon5: 28
taicho-fujiyama: 27
ko-chanblog95: 27
azu-ryugaku: 27
setsuyakufufu: 27
karin88: 26
esupro: 26
mentalx: 26
nie3: 26
titirobo: 26
gqp: 25
chippyofficial: 25
TAKOICHI: 25
Hikaru-English: 25
hal7pi: 25
sara_pezzini: 25
WhiteTree: 24
ishideo: 24
tai_mijinko: 24
otasuke0411: 24
myprivatecomedy: 23
takahon: 23
miwamomoka: 23
for-mom: 23
sorairo2000: 23
sekiuti: 23
dutch2017: 23
nottoworry-money: 22
c6amndbgr3: 22
ev_traveler: 21
salondealfurd: 21