[Work/Class/Python3の基礎とデータ処理/python_basic]

HTMLParserクラスによるWebページの取得と分析

HTMLParserクラス

概要

HTMLParserクラスは,Python3の同梱標準ライブラリであるhtml.parserライブラリの中に含まれるクラスで,文字列として渡されたHTMLを1次元json方式で順に読んで行き,タグの開始時,タグが何か文字列を囲んでいる場合,タグの終了時にそれぞれイベント関数を呼び出してくれる.

HTMLParserクラスを継承したクラスを設計し,それぞれのイベント関数をオーバーライドすることで,様々な分析を行うことができる.

オーバーライドすべきイベント関数

handle_starttag(self, tag, attrs)
タグが開始された時に呼ばれるイベント関数.tag変数にはタグの名前が入っており,attrs変数にはそのタグのアトリビュート(属性,例えばaタグならhref="http://cad.lolipop.jp" title="..."など)が,入れ子listの形で格納されている.
handle_data(self, data)
タグで囲まれた文字列(要素, element)がdataタグに入っている.開始タグイベントの後に,何らかの要素が入っていれば呼ばれる.
handle_endtag(self, tag)
タグ終了時に呼ばれる.tagにはタグの名前が入っている.1次元json方式で読んでいくので,開始されたタグの終了を知らせるイベント関数が必要になる.

urllib.request.urlopen()関数

概要

urllibはPython3の同梱標準ライブラリに含まれている.

引数に「http://...」のようにURL文字列を入れると,HTTP GETの結果をHTTPResponseクラスのオブジェクトインスタンスとして返してくれる.

HTTPResponseオブジェクトインスタンスから情報を得る

返されたオブジェクトインスタンスから情報を得るには以下のようにする.

レスポンスインスタンス.code
HTTPのレスポンスコードが入っている.この中身が200なら正常に取得して終了した,という意味.
レスポンスインスタンス.info().get_content_charset()
取得してきたページの文字コードを返す関数.変数に突っ込んでおく.
レスポンスインスタンス.read().decode(文字コードを格納した変数)
取得してきたページを文字列として返す関数.read()だけだとバイト配列(バイナリ配列)を返すので,事前に変数に突っ込んでおいた文字コードを使ってデコードする.

コード例

ページ中に特定の文字列が何回登場するか調べる

# -*- coding: utf-8 -*-
import urllib.request 
from html.parser import HTMLParser

# HTMLParserクラスは,イテレータ的に上からタグを読んでいくクラス
# handle_starttag, handle_data, handle_endtag
# メソッドをそれぞれ継承して定義する
class MyParser(HTMLParser): #HTMLParserを継承したクラスを定義

    # 定数やインスタンス内で値が変わらないstaticメンバ変数はクラス宣言の後に宣言する
    # 頭に「__」が付くと変数も関数もprivate扱いになる
    def __init__(self):
        # コンストラクタ
        super().__init__() #親クラスのコンストラクタを実行
        self.__counter = 0
        # 実行中に変わることが前提の通常のインスタンスメンバ変数は
        # コンストラクタ内で「self.変数名」で宣言し,初期化する

    # Override
    def handle_starttag(self, tag, attrs):
        print('Start Tag:', tag)

    # Override
    def handle_data(self, data):
        # テキストデータがタグで囲まれている時に呼び出される
        # どうやらスペースが入ってても呼び出されるらしい
        # なので最初に,' '(半角スペース)を''(何も入っていない文字)で置き換える
        new_data = data.replace(' ', '')
        if len(new_data) > 0:
            print('Some Data:', new_data)
            if 'こーひー' in new_data:
                self.__counter += 1

    # Override
    def handle_endtag(self, tag):
        print('End Tag:', tag)

    def getCounter(self):
        return self.__counter

if __name__ == "__main__":
    url = 'http://cad.lolipop.jp'
    gotten_http_response = urllib.request.urlopen(url) 
    # 指定されたURLのページを取得する命令を出し,
    # その結果をgotten_http_responseという名前の
    # HTTPResponseクラス型の変数に突っ込む
    
    # 各種情報の出力
    print(gotten_http_response.code)
    #print(gotten_http_response('content-type'))
    charset = gotten_http_response.info().get_content_charset()
    print(charset)

    # GETしてきたHTML自体を出力
    # HTTPResponseクラスはread()という関数で中身を読めるのだが,
    # これはbyte型(つまりバイナリデータ)なので,文字列に変換するため
    # utf-8エンコードを指定してデコードする
    # print(gotten_http_response.read().decode('utf-8'))

    myParser = MyParser() #上で宣言していたクラスのオブジェクトインスタンス化
    myParser.feed(gotten_http_response.read().decode(charset)) 
    #パーサにHTMLを渡してjson構造をイテレータ動作で読んでいく
    
    print('Word "こーひー" appear', myParser.getCounter(), 'times')

    myParser.close() #デストラクタを呼ぶ
    gotten_http_response.close()

属性を取得し,別サイトへのリンクが何回あるかを調べる

# -*- coding: utf-8 -*-
import urllib.request 
from html.parser import HTMLParser

class GetLink(HTMLParser): #HTMLParserを継承したクラスを定義
    def __init__(self, my_domain):
        # コンストラクタ
        super().__init__() #親クラスのコンストラクタを実行
        self.__counter = 0
        self.__link_url_array = [] #リンク先を格納する配列の初期化
        self.__my_domain = my_domain

    # Override
    def handle_starttag(self, tag, attrs):
        # tagはタグ名文字列
        # attrsはアトリビュート(要素)が入れ子配列
        # ([['href', 'http://...'], ['title', 'こーひーをぶんなぐれ'], ...]
        # のような形で出てくる
        if tag == 'a':
            # 入れ子配列で出てくるアトリビュートをハッシュテーブルに変換
            attrs_of_a_tag = dict(attrs) 
            # ハッシュテーブルにhref
            if 'href' in attrs_of_a_tag:
                link_address = attrs_of_a_tag['href']
                if 'http://' in link_address:
                    # 文字列'http://'を含んでいて
                    if self.__my_domain not in link_address:
                        # それがサイト内リンクではない場合,
                        # 配列に突っ込んでカウンタを増やす
                        self.__link_url_array.append(link_address)
                        self.__counter += 1
                        print(link_address)

    # Override
    def handle_data(self, data):
        new_data = data.replace(' ', '')
        if len(new_data) > 0:
            pass

    # Override
    def handle_endtag(self, tag):
        pass

    def getCounter(self):
        return self.__counter
                    
if __name__ == "__main__":
    url = "http://cad.lolipop.jp"
    gotten_http_response = urllib.request.urlopen(url) 
    # 指定されたURLのページを取得する命令を出し,
    # その結果をgotten_http_responseという名前の
    # HTTPResponseクラス型の変数に突っ込む
    
    # 各種情報の出力
    print(gotten_http_response.code)
    #print(gotten_http_response('content-type'))
    charset = gotten_http_response.info().get_content_charset()
    print(charset)

    # GETしてきたHTML自体を出力
    # HTTPResponseクラスはread()という関数で中身を読めるのだが,
    # これはbyte型(つまりバイナリデータ)なので,文字列に変換するため
    # utf-8エンコードを指定してデコードする
    # print(gotten_http_response.read().decode('utf-8'))

    getLink = GetLink(url) #上で宣言していたクラスのオブジェクトインスタンス化
    getLink.feed(gotten_http_response.read().decode(charset)) 
    #パーサにHTMLを渡してjson構造をイテレータ動作で読んでいく
    
    print('hyper link to other site appears', getLink.getCounter(), 'times')

    getLink.close() #デストラクタを呼ぶ
    gotten_http_response.close()