トップページ -> Pythonで文字化けチェックとリンク切れチェックをしたい

Pythonで文字化けチェックとリンク切れチェックをしたい

今回はPythonでウェブサイトの文字化けとリンク切れのチェックをしてみたいと思います. リンク切れのチェックだけならリンクチェッカー(リンク切れチェックツール)で簡単に行うことができるのですが, 文字化けのチェックとなるとなかなか便利なものが見つかりませんでした. 今回は静的なページに対して,ローカルに保存されているファイルと比較して内容が変わっていないかを比較することでリンク切れチェックのついでに文字化けをしているかもしれないページを探し出します.

変数の設定とライブラリのインポート

requestsでサーバーから取得したhtmlをBeautifulSoupで解析しトップページから到達することのできるページのリンク切れを走査します. ドメイン名と文字化けチェックを行う場合はサイトのファイルのルートを指定します. サーバーの負荷を抑えるために待機時間を適宜指定してください.


import requests
from bs4 import BeautifulSoup
import time

# WebサイトのURLとルートを指定
DOMAIN = "ここにリンクチェックをしたいサイトのドメインを入力" # このサイトならhttp://mikami3345.html.xDOMAIN.jp
ROOT = "ここにサイトのファイルのルートを入力(ローカル)" # C:/Users/User/Desktop/myWebSite など
wait_time = 5 # サーバーへの負荷を抑えるために待機時間を適宜設定してください

url = DOMAIN # urlをDOMAINで初期化しておく(全てのページの判定を行うにはトップページから全ページに到達できる必要があります)

パスとURLを読み替える関数の定義

相対パスで指定されているアンカーをURLに直す関数と,ローカルファイルのパスに直す関数(文字化けチェック用)です.


# 相対パスで指定されているものをURLに直す関数
def change_url(url,next_url,DOMAIN):
    if "#" in next_url:
        pos = next_url.find("#")
        next_url = next_url[:pos]
    
    # ページ内のアンカーの場合
    if len(next_url) == 0:
        return url
    # ルートからパスを指定している場合
    elif next_url[0] == "/":
        return DOMAIN + next_url
    # URLがそのまま書いてある場合
    elif "http" == next_url[:4]:
        return next_url
    # 下層のページの場合
    elif "../" not in next_url:
        if DOMAIN == url:
            return DOMAIN + "/" + next_url
        else:
            current_dir = url.split("/")
            current_dir.pop() # 今いるページは外す
            return "/".join(current_dir) + "/" + next_url
    # 上層のページの場合
    else:
        current_dir = url.split("/")
        current_dir.pop() # 今いるページは外す
        while "../" in next_url and current_dir:
            current_dir.pop()
            next_url = next_url[3:]

        return "/".join(current_dir) + "/" + next_url
    
# ローカルファイルにアクセスするときのパスを返す関数
def get_tail(url,DOMAIN):
    if url == DOMAIN:
        return "/index.html"
    return url[len(DOMAIN):]

全てのページを走査する

result_dict[url]にステータスと文字化けしているか否かを記録します. また,リンク切れを修正するためにpage_dict[url]にurlを含むページを記録します.


# 調査結果の記録用の変数
done_set = set() # 調査済みのURLのセット(二重に調べることを防ぐため)
result_dict = {} # 調査結果の辞書
page_dict = {} # key を含むページの辞書
url_list = [url] # 操作するURLのリスト
cnt = 0 # 調査したページ数のカウント

# url_listが空になる(全てのページの調査が終わるまでループ)
while url_list:
    url = url_list.pop() # urlを一つ取り出す
    cnt += 1
    
    # print(f"#{url}を調査中")
        
    # Requestsを利用してWebページを取得する
    try:
        r = requests.get(url)
    except:
        # リクエストに失敗した場合はNG(リンク切れ)
        result_dict[url] = {}
        result_dict[url]["status"] = "Request Failed"
        result_dict[url]["is_garbled"] = "???"
        
        time.sleep(wait_time)
        print("======================")
        print(f"調査済み:{cnt}, 残り:{len(url_list)}")
        continue

    is_garbled = False
    # 自分のサイトであればページ内のリンクを調べる必要がある
    if DOMAIN in url:
        soup = BeautifulSoup(r.content, 'html.parser')
        is_garbled = "???"
        try:
            # ローカルファイルにアクセスしサーバー上のファイルと比較する
            tail = get_tail(url,DOMAIN)
            # 拡張子がhtmlでなければ文字化け判定をしない
            if tail.split(".")[-1] == "html":
                is_garbled = False
            else:
                soup_local = BeautifulSoup(open(ROOT+tail,encoding="utf-8"), 'html.parser')
                if soup_local == soup:
                    is_garbled = False # 同じであれば文字化けは起きていない
                else:
                    is_garbled = True
        except:
            # ローカルファイルにアクセスできないときは何もしない is_garbled = "???" のまま
            pass
        
        # find_all で aタグをすべて探す
        elems = soup.find_all("a")
        for e in elems:
            # 要素にhrefが設定されていない場合はエラーになるのでtryで囲んである
            try:
                next_url = e["href"]
                next_url = change_url(url,next_url,DOMAIN)
                
                if next_url not in page_dict:
                    page_dict[next_url] = []
                page_dict[next_url].append(url)
                
                if next_url not in done_set:
                    url_list.append(next_url)
                    done_set.add(next_url)
            except:
                pass
            
    
    result_dict[url] = {}
    result_dict[url]["status"] = r.status_code
    result_dict[url]["is_garbled"] = is_garbled
        
    print(f"#{url}: {result_dict[url]}")

    time.sleep(wait_time)
    print("======================")
    print(f"調査済み:{cnt}, 残り:{len(url_list)}")

結果の出力

URLのstatusが200でなければリンク切れの可能性があるとしてそのリンクが含まれるページを表示します. また,文字化けをしている可能性があれば文字化けしている可能性があることを出力します.


for url in result_dict:
    data= result_dict[url]
    status = data["status"]
    is_garbled = data["is_garbled"]
    if status != 200:
        print(f"#{url}がリンク切れの可能性 {status}")
        print(f"#{url}を含むページのリスト")
        for u in page_dict[url]:
            print(page_dict[u])
        print("===================")
    if is_garbled != False:
        print(f"#{url}に文字化けの可能性")
        print("===================")

あまり嬉しくはないですが,たくさんのリンク切れを見つけることができました. 時間があるときに直しておこうと思います.

リンク切れチェックの結果
リンク切れチェックの結果