うつブログ分類器をつくってみた(準備編)

なにかしらアウトプットしようかなとおもってブログはじめました。
続く気がしません。

やりたいこと

自然言語処理をつかってブログをうつ病と正常の2クラスに自動で分類したい

おおまかな手順

  1. ブログ村メンタルヘルスランキング に掲載されているブログからスクレイピング <-- 今回はここだけ
  2. 取得したhtmlからブログ毎に名詞のみ抽出 (BoW)
  3. TfIdfなどで前処理してモデルにつっこむ
  4. 結果の解釈

準備編と称してブログスクレイピング(クローリング)の部分を書いてみます.
まあ大層なことしてないんで書かなくてもいいかなと思ったんですが、自分用メモ&ブログ書く準備体操を兼ねて、ということで。

2,3,4は次回書きます

クローリング

ランキングサイトから各ブログのURLを一気に取得

from selenium import webdriver
import re

options = webdriver.chrome.options.Options()
options.add_argument("--headless")
driver = webdriver.Chrome(chrome_options=options)

def collecting_blog_url(ranking_url):
    driver.get(ranking_url)
    rankings = driver.find_elements_by_xpath("//li[@class='entry-rankingtitle']")
    links = [ranking.find_elements_by_xpath("./a")[0] for ranking in rankings]
    urls =[]
    for link in links:
        url = link.get_attribute('href')
        # ブログURLのみを抽出, url デコード
        url = urllib.parse.unquote(re.findall(r"url=(.*)$",url)[0])
        urls.append(url)
    return urls

最初BeautifulSoupでスクレイピングしてみたらすかすかのhtmlがかえってきました.
これはこのサイトのhtmlがjavascriptレンダリングされているのが理由ですね.

そういうときはseleniumを使うといいらしいです. seleniumxpathなどを用いて要素抽出できるので、ここではそうしています.

Python+Selenium+Chrome(+BeautifulSoup)でスクレイピングする を参考にさせていただきました)


取得したurlをブログサービスごとに整理

def arranging_url_by_blog_type(urls):
    arranged_urls={'fc2':[],'ameblo':[],'goo':[],'hatena':[]}
    for url in urls:
        #末尾を / に統一
        if url[-1] !='/':
            url += '/'
        
        if re.match(r'.*?fc2',url):
            arranged_urls['fc2'].append(url)
        elif re.match(r'.*?ameblo',url):
            # スマホ用urlをPC用urlに統一
            url = re.sub('s\.ameblo','ameblo',url) 
            arranged_urls['ameblo'].append(url)
        elif re.match(r'.*?goo',url):
            arranged_urls['goo'].append(url)
        elif re.match(r'.*?hatenablog',url):
            arranged_urls['hatena'].append(url)
    
    return arranged_urls

これはこの次の記事スクレイピングのための作業です.

https://alotofthings88.hatenablog.com/ の中に'hantenablog'が含まれているように、ブログurl中に各blogサービスの名前が絶対入っているので、それを正規表現で一々チェックしています.

今回はfc2, ameblo, hatena, goo,しかチェックしていませんが、、

f:id:alotofthings88:20180602142931p:plain

これだけでも半分くらいのブログをカバーできたので、とりあえずよしとしました.
泥臭い...もっと綺麗に書けないかな...


blogサービス毎に各記事のurlを一気に取得

import time
import requests
import chardet

def getting_html(url):
    response = requests.get(url,headers=headers)
    time.sleep(1) # サーバー負荷回避
    encoding = chardet.detect(response.content)['encoding']
    html = response.content.decode('utf-8')
    return html


def collecting_articles_url(blog_url, blog_type):
    article_urls =[]

    if blog_type=='fc2':
        #fc2は全記事一覧ページが存在
        index_url = blog_url + 'archives.html'
        html = getting_html(index_url)
        article_urls.extend(re.findall(blog_url+r'blog-entry-.*?\.html',html))

    elif blog_type=='ameblo':
        #amebloは1page20件の一覧ページが存在
        page = 1
        while True:
            index_url = blog_url + 'entrylist-' + str(page) +'.html'
            html = getting_html(index_url)
            article_urls.extend(re.findall(blog_url+r'entry-.*?\.html',html))
            nums = re.findall(blog_url+r'entrylist-([0-9]*)\.html',html)
            page_index = [int(num) for num in nums]
            if page + 1 in page_index :
                page +=1  
            else:
                break
      
    return article_urls

# 長いのでhatena と goo は省略

記事の本文を抽出する前に、各記事のurlを取得する必要があります.
ここはいろいろ迷ったんですが、この4つのブログサービスは記事一覧ページが存在しているのでそれを利用しました.

fc2の場合はブログURL + archives.html で全記事一覧が見れます. 超便利.
fc2以外は 1ページに20件ずつ表示とかなのでページを進ませるロジックを書く必要があります.
ちなみにhatenaは https://alotofthings88.hatenablog.com/archive?page=1 というかんじです

やっぱり泥臭い...


各記事URLから本文とタイトルのテキストを抽出する

from extractcontent3 import ExtractContent

def extract_article_text(article_url):
    extractor = ExtractContent()
    opt = {"threshold":50}  # オプション値を指定する
    extractor.set_option(opt)

    extractor.analyse(getting_html(article_url))
    text = [text.strip() for text in extractor.as_text()]
    content = text[0]
    title = text[1]
    return content, title

ようやく記事URLも集め終わってよしテキストを手に入れるぞ!って思ったんですが、
「あれ、URLから本文のみを抽出するってどうやるんだ?」 との疑念が浮かびけっこう焦りました(今更)

いやもちろん、さっきと同じようにxpathやらidを使ってスクレイピングすれば出来ないことはないんですが、これが厄介なことにブログテンプレート毎にhtml構造も違えばid名class名もまちまちなんですよね。
まじかー...

そう思って調べていると、本文抽出ライブラリなるものが存在することが分かりました。
PythonでブログのHTMLから本文抽出 2015 - orangain flavor

このサイトに本文抽出ライブラリの比較表があるのですが、それを見る限り一番無難そうだったextractcontent3を使いました.

試しに抽出してみると、

f:id:alotofthings88:20180602143408p:plain (内容がわからないように縮小してます)

いい感じ. ほんと便利な世の中ですね!
というわけでスクレイピング編おわり、

結果編につづく....