Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Python+Django+AWSでスクレイピングアプリを作って転職

More than 1 year has passed since last update.

ITベンチャーの経営企画室在職のmogkenです。
ここ最近転職を考えるようになり、その際のアピールになればとプログラミングの勉強をしています。
口で勉強していると言うだけでは大したアピールにならないので、簡単なWebアプリをPythonとDjangoで作ってAWS上に構築&Githubにソースを公開しました。

今回は自分の中での知識の定着のためのアウトプットを兼ねて自分で書いたプログラム(Python)の解説を行いたいと思います。作った時期としてはPythonを勉強して3ヶ月目で、一からこうしたものを作るのは初めてです。もし、コードの改善点などアドバイス頂ける方がいましたらコメントして頂けると嬉しいです。

今回の完成物

企業口コミサイト2つ+上場企業情報をスクレイピングしてきて表示、という感じのWebアプリを転職の時の企業比較の一助になればと願いを込めて作成。
最終的にあんまり使いませんでした...orz
img80.jpg

概要

UIはBootstrapを使ってあまり手間をかけずにサクッと作成
フレームワークはDjangoを使用
サーバはAWSのAmazonLinux上に構築

UI 言語 フレームワーク サーバ
Bootstrap4 Python Django AWS(Amazon Linux + Nginx)

プログラムの詳細

プログラムの流れ

・検索したい企業名を検索窓に入力
・スクレイピング対象サイトから検索企業が載っている情報(HTML)を取得
・取得した情報(HTML)から必要情報だけを抜き出し(パース)
 -必要情報
  企業の口コミポイント
  社員数などの情報(上場企業の場合)
・検索結果としてまとめて表示

対象サイトから検索企業が載っている情報(HTML)を取得

対象サイトの情報取得(スクレイピング)するためにBeautifulsoupを使用

searchCompany.py
#パース対象サイトをここに追加
targetSite =['vorkers', 'hyoban', 'jyoujyou']

class GetHtml:
    """
    GetHtml as text
    """
    # 検索サイトのURL登録
    def __init__(self, company):
        self.headers = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0",}
        self.urls = {
            targetSite[0]:'https://www.vorkers.com/company_list?field=&pref=&src_str={}&sort=1&ct=top'.format(company), 
            targetSite[1]:'https://en-hyouban.com/search/?SearchWords={}'.format(company),
            targetSite[2]:'https://上場企業サーチ.com/searches/result?utf8=✓&search_query={}'.format(company)
        }

    # 対象ページのHtml全文を取得
    def getText(self):
        textList = {}
        for url in self.urls:
            res = requests.get(self.urls[url], headers=self.headers)
            text = bs4.BeautifulSoup(res.text, "html.parser")
            textList.setdefault(url, text)
        return textList

init

self.headers
Webブラウザから情報を取得しようとしていますよと偽装するために設定
対象サイトのサーバがスクレイピングしようとしていると認識するとブロックされてしまう場合があるためWebブラウザからのアクセスであることを偽装する必要がある。

self.urls
スクレイピング対象サイトの検索ページURLを設定
ユーザが入力した企業名を各サイトのクエリストリング(?=)以下の所定の場所に引数として渡すことで各サイトから企業の情報を取得

(クエリストリング)
urlの末尾にサーバに送信したい情報を記載する際の特定の文字列。?=などで始まることが多い。
サイトで特定のキーワードを検索する場合、ユーザが検索したいキーワードはクエリストリング以下に記載されてサーバに送信される。サーバはその特定の文字列から検索したいワードを識別して必要な情報を送り返し、ブラウザがその情報を表示する。

つまりクエリストリング以下に検索したいキーワードを入力すれば検索窓に文字を打たなくても検索できるということ。

getText

targetSite(スクレイピング対象サイト)をキー、取得したHTML情報バリューとした辞書を返す関数
取得した情報(HTML)を辞書に格納し、それを次に書く処理に渡して欲しい情報だけ抜き出していく。

必要情報の抜き出し(from口コミサイト)

対象サイト:openWork(旧:Vorkers) / カイシャの評判
必要情報:評価ポイント/企業名

searchCompany.py
class ParseHtml:
    """
    ParseHtmlHtml to get required values
    """
    # 企業名と評価ポイントの取得
    def parseNamePoint(self, textList):
        #パース用のタグ登録
        nameTag =  {
            targetSite[0]:["h3", "fs-18 lh-1o3 p-r"],
            targetSite[1]:["h2", "companyName"],
        }
        pointTag = {
            targetSite[0]:["p", "totalEvaluation_item fs-15 fw-b"],
            targetSite[1]:["span", "point"],
        } 

        comNamePoint = {}
        for site in targetSite[:2]:
            try:
                #会社名の取得
                parseCname =  textList[site].find(nameTag[site][0], class_=nameTag[site][1])
                cname = parseCname.getText().replace('\n','').replace(' ', '')

                #会社評価ポイントの取得
                parseCpoint = textList[site].find(pointTag[site][0], class_=pointTag[site][1])              
                cpoint = parseCpoint.getText().replace('\n','').replace(' ', '')

            # 検索結果が無かった場合の処理
            except AttributeError:
                comNamePoint.setdefault(site, ['結果なし','結果なし'])

            # 検索結果が有った場合の処理
            else:
                comNamePoint.setdefault(site, [cname, cpoint])

        return comNamePoint

parseNamePoint(self, textList)

nameTag
会社名取得のためのHTMLタグを記載

pointTag
口コミ評価を取得するためHTMLタグを記載

(HTMLタグ)
ブラウザはサーバから送られてくるHTML情報を元にWebページを表示している。
そこで必要情報を

for文内
入力された企業名をクエリストリングに入れたURLを対象にスクレイピングする処理にしているため、企業名によってはページが存在しない場合がある。そのための例外処理をここで行っている。

-例外処理概要
・検索結果がある場合はtargetSiteをキーに評価ポイントと企業名をバリューにした辞書を返す
・検索結果がない場合のエラーAttributeErrorを検知すると、バリューが'結果なし'になる辞書を返す

必要情報の抜き出し(from上場企業サーチ)

対象サイト:上場企業サーチ
必要情報:会社名/業種/従業員数/平均年齢/平均勤続年数/平均賃金

searchCom.py
# 上々企業である場合、企業詳細の取得
    def parseInfo(self, textList):
        #パース用のタグ登録
        cnumberTag = {
            targetSite[2]:['dl', 'well'],
        }
        cinfoTag = {
            targetSite[2]:['dd', 'companies_data']
        }

        comInfo = {}

        #企業名から企業詳細URLの取得        
        try:
            parseCnumber =  textList[targetSite[2]].find(cnumberTag[targetSite[2]][0], class_=cnumberTag[targetSite[2]][1])
            cnumber = parseCnumber.getText()
            cname = mojimoji.han_to_zen(cnumber[5:].replace('\n', '').replace(' ', ''))
            detail = 'https://xn--vckya7nx51ik9ay55a3l3a.com/companies/{}'.format(cnumber[:5])
        # 検索結果が無かった場合の処理
        except AttributeError:
            comInfo.setdefault(targetSite[2], ['データ無し','','','','','',''])
        # 検索結果が有った場合の処理
        else:
            #企業詳細ページのHtml取得
            res = requests.get(detail)
            text = bs4.BeautifulSoup(res.text, "html.parser")

            #企業詳細ページのパース
            parseCinfo =  text.find_all(cinfoTag[targetSite[2]][0], class_=cinfoTag[targetSite[2]][1])
            cinfo = parseCinfo

            #パースした内容の取得
            cinfoList = []
            for info in cinfo:
                infoText = info.getText().replace('\n', '').replace('\t', '')
                cinfoList.append(infoText)
            #企業名の追加
            cinfoList.append(cname)

            if len(cinfoList) <= 18:
                cinfoList.append('')

            #必要情報の成形
            useList = itemgetter(0,10,14,15,16,17,18)(cinfoList)
            comInfo.setdefault(targetSite[2], useList)

        return comInfo

def parseInfo(self, textList)

cnumberTag
上場企業サーチは単純にクエリストリング以下に企業名を入力するだけでは企業詳細が取得できるのURLとはならないため、cnumberTagには詳細情報が取れるページのURLを取得するためのHTMLタグを指定

cinfoTag
企業詳細ページから必要情報を取得するためのタグを指定

try,except(例外処理)*
ここでも検索結果がない場合があるのでその場合の例外処理を行っている。

for文
返り値としてtargetSiteをキーとして、必要な情報のリストがバリューの辞書が返せるようにデータを加工

検索結果としてまとめて表示

searchCompany.py
def main(company):
    aboutCompany = {}

    #URLとHtmlの取得
    getHtml = GetHtml(company)
    text = getHtml.getText()
    urls = getHtml.urls

    #htmlのパース
    parseHtml = ParseHtml()
    comNamePoint = parseHtml.parseNamePoint(text)
    comInfo = parseHtml.parseInfo(text)

    #出力データの成形
    #企業名と評価ポイント
    for site in targetSite[:2]:
         comNamePoint[site].append(urls[site])
    aboutCompany.update(comNamePoint)

    #企業詳細情報
    for info in comInfo:
        aboutCompany.setdefault(info, comInfo[info])

    #検索ワード
    words = mojimoji.han_to_zen(company)
    aboutCompany['searchWord'] = words


    return aboutCompany

if __name__ =="__main__":
    print(main('ソフトバンク'))

main(company)

aboutCompany
入力された企業名を引数にHTMLの取得とパースを行い、それをaboutCompanyという名前の辞書に格納して変数として返している。あとはDjangoでいい感じに指定してHTMLで表示させる処理をしている。

mojimoji
mojimojiという半角文字を全角文字に直してくれる外部ライブラリーを使用
情報を引っ張ってくるサイトによっては半角と全角が統一されていないため、一括で変換している。

終わりに

今回の解説は以上です。
一ヶ月以上前に完成したプログラムなので細部を忘れてしまっていて大変でした。汚いコードを読み返す辛さってこれのことかと自分のコードで気がついてしまい何とも言えない気持ちです...

次はDjangoとAWS編を書こうと思います。

mogken15
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away