Python
スクレイピング
自然言語処理
DeepLearning
word2vec

検索結果スクレイピング&doc2vecで企業の業種分類(1)

アドテク企業に転職したら、顧客の業種分類コードが存在していませんでした。集計で関連業種を目視でピックアップしたらえらく時間がかかってしまったので、今後のためにアドテク的に自動分類する方法を考えました。
手元には顧客名と配信した広告のLPのURLしかありません。
どんなクラスタ数になるのかもよくわからないので、企業を説明する文章を外部から取得しベクトル化したうえで、デンドログラムで階層化するのが今回のゴールです。

今回の構成

Google検索結果からのサイトURLの取得&検索結果テキストの取得

技術的には使い古されているのですが、実際に検索結果のテキストを使おうとするとノイズの少ないデータを取ってくるのが結構難しいので、参考になれば幸いです。

データ

企業ID 企業名 URL
0000 企業名企業名 https://xxxxx
  • 外国の企業も混在
  • URLにはNULLが存在
  • すでに閉鎖したサイトも存在

ベースとなるパート

ライブラリのインポート

BeautifulSoupと正規表現(re)を入れます。

import urllib.request
import urllib.parse
from bs4 import BeautifulSoup
import csv
import os
import re

ファイルを読み込む

path = "./keywords"
fo = open('myfile.csv', 'rt', encoding='utf-8')
dataReader = csv.reader(fo)

beautifulsoupでクロールし、1企業ごとにテキストファイルを出力する

  • 検索結果の説明文部分(span.st)のみを抽出
  • すでにファイルが存在している場合はスキップ
  • 検索結果30件、日本語の結果のみに指定
  • サイト内検索を利用(普通に検索すると、求人サイトばかり引っかかるため)
for row in dataReader:
    filename = row[0] + ".txt"
    if os.path.isfile(filename) == False:
        opener = urllib.request.build_opener()
        opener.addheaders = [('User-agent', 'Mozilla/5.0')]
        url_company = row[2]
        url_result = "https://www.google.co.jp/search?num=30&lr=lang_ja&q=site:" + urllib.parse.quote(url_company)
        html = opener.open(url_result)
        bs_result = BeautifulSoup(html.read(), "lxml")
        if len(bs_result.find_all("span",{"class":"st"})) > 1:
            result = "\n".join([re.sub("['\n\.]", "", x.get_text()) for x in bs_result.find_all("span",{"class":"st"})])
        else:
            continue

        #結果の書き込み
        fw = open(path + "/" + filename,'w', encoding='utf-8')
        fw.write(result)
        fw.close()

基本的には難しいことはないのですが、いくつかトラブル発生。

追加箇所

上のfor文中、url_companyを定義している部分を以下に書き換えました。
- 対策1:URLがNULLの場合は、検索結果から1位のURL(div#res.cite)からドメインのみを抽出
- 対策2:wikipediaが1位に来る場合は次の結果から抽出
- 対策3:0件ヒットはスキップ
- 対策4:日本語サイトらしきURL(jaとか入っているディレクトリ)は残す

if not row[2]:
            #検索結果の読み込み
            url_site = "https://www.google.co.jp/search?num=3&q=" + urllib.parse.quote(re.sub("株式会社", "", row[1]))          
            html = opener.open(url_site)
            bs_site = BeautifulSoup(html.read(), "lxml")
            if not bs_site.find_all("div",{"id":"res"})[0].get_text(): #0件ヒット
                continue
            elif len(re.findall("wiki", bs_site.find_all("div",{"id":"res"})[0].cite.get_text())) == 0: #wikipediaが1位
                if len(re.findall("/[^\./]*(ja)|(jp)[^\./]*$", bs_site.find_all("div",{"id":"res"})[0].cite.get_text())) > 0: #日本語サイト
                    url_company = bs_site.find("div",{"id":"res"}).cite.get_text()
                else:
                    url_company = re.sub("(\.[a-z]+)/.+$", "\\1", bs_site.find("div",{"id":"res"}).cite.get_text())
            else:
                url_company = re.sub("/[^\.]+", "", bs_site.find("div",{"id":"res"}).find_all("cite")[1].get_text())
        else:
            url_company = row[2]

最終的なfor以降のソース

for row in dataReader:
    filename = row[0] + ".txt"
    if os.path.isfile(filename) == False:
        opener = urllib.request.build_opener()
        opener.addheaders = [('User-agent', 'Mozilla/5.0')]

        if not row[2]:
            #検索結果の読み込み
            url_site = "https://www.google.co.jp/search?num=3&q=" + urllib.parse.quote(re.sub("株式会社", "", row[1]))          
            html = opener.open(url_site)
            bs_site = BeautifulSoup(html.read(), "lxml")
            if not bs_site.find_all("div",{"id":"res"})[0].get_text(): #0件ヒット
                continue
            elif len(re.findall("wiki", bs_site.find_all("div",{"id":"res"})[0].cite.get_text())) == 0: #wikipediaが1位
                if len(re.findall("/[^\./]*(ja)|(jp)[^\./]*$", bs_site.find_all("div",{"id":"res"})[0].cite.get_text())) > 0:
                    url_company = bs_site.find("div",{"id":"res"}).cite.get_text()
                else:
                    url_company = re.sub("(\.[a-z]+)/.+$", "\\1", bs_site.find("div",{"id":"res"}).cite.get_text())
            else:
                url_company = re.sub("/[^\.]+", "", bs_site.find("div",{"id":"res"}).find_all("cite")[1].get_text())
        else:
            url_company = row[2]

        url_result = "https://www.google.co.jp/search?num=30&lr=lang_ja&q=site:" + urllib.parse.quote(url_company)
        html = opener.open(url_result)
        bs_result = BeautifulSoup(html.read(), "lxml")
        if len(bs_result.find_all("span",{"class":"st"})) > 1:
            result = "\n".join([re.sub("['\n\.]", "", x.get_text()) for x in bs_result.find_all("span",{"class":"st"})])
        else:
            continue

        #結果の書き込み
        fw = open(path + "/" + filename,'w', encoding='utf-8')
        fw.write(result)
        fw.close()

注意事項

Googleのレスポンスには上限件数があるので、大量にリクエストを投げると終了します。
本格的にやる場合はサーチAPIを契約するのが良いと思います。

これらの処理により、指定したフォルダ内に企業名毎のテキストファイルが作成されます。
次回はこのファイルをdoc2vecにかけて学習し、任意のキーワードや企業と近しい企業が抽出できるようにします。