知人に頼まれ、5分おきに取得された約2年分25万のhtmlから特定の部分のみ抽出してCSVに出力する作業を行ったんですが、文字コードエラーに苦しめられたのでどう対応したかを備忘録的に残しておきます。
1.どんなエラーだったのか
抽出対象のhtmlはローカルに保存されていますので以下のような感じで、Beautifulsoup4でパースして抽出していました。
from bs4 import BeautifulSoup
soup = BeautifulSoup(open(LOCAL_FILE_PATH),"html.parser")
#以下、処理が続く
最初は順調だったんですが、あるファイルから以下のようなエラーを吐くように...
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe3 in position 15002: invalid continuation byte
「UTF-8でデコードできない」というエラーということなので、chardetを使って文字コード判定してみるもconfidenceは40%程度。推定された文字コードを指定してopen()で開いても状態は変わらずでした。
Latin1という文字コードになっている可能性もあるというブログ記事を見つけたので早速やってみるものの、エラーは出ないが中身が文字化けしていて使えない。
では、エラーを無視してしまおうということで、以下のようにopen()にerrors='ignore'という引数を追加してみました。
soup = BeautifulSoup(open('LOCAL_FILE_PATH', errors='ignore'),"html.parser")
これでとりあえず動くは動いたんですが、明らかにhtmlファイルの一部分しか読み込まれていません。どうやらUTF-8と不明な文字コードが混ざったhtmlのようです。
ほんのちょっとであればこのまま進めようかとも思ったのですが、htmlファイルの情報の2/3くらいが不明な文字コードだったらしくほぼデータが取れておらず、しかも残り15万ファイルがすべて同じ状況でしたので、他の手を考えることにしました。
2.Seleniumで一度Chromeに表示させたhtmlを再取得して解決
エラーが出ているhtmlファイルをChromeで開くと何故か文字化けもせずに開けていたので、一度Chromeで表示させたhtmlを取得することにしました。
from bs4 import BeautifulSoup
#Seleniumを使うため各種モジュールをimport
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.select import Select
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.alert import Alert
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
# Seleniumをあらゆる環境で起動させるChromeオプション
options = Options()
options.add_argument('--disable-gpu');
options.add_argument('--disable-extensions');
options.add_argument('--proxy-server="direct://"');
options.add_argument('--proxy-bypass-list=*');
options.add_argument('--start-maximized');
options.add_argument('--headless'); # ※ヘッドレスモードを使用しない場合、コメントアウトする
#別途ダウンロードしたchromedriverのパスを指定
DRIVER_PATH = 'chromedriver'
#ブラウザの起動
driver = webdriver.Chrome(executable_path=DRIVER_PATH, chrome_options=options)
#ローカルHTMLをChromeで開く
URL = "file://" + LOCAL_FILE_PATH #Chromeで開けるよう"file://"を頭につける。
driver.get(URL)
#Chromeで表示されたhtmlを取得
html = driver.page_source
#BeautifulSoupに渡す
soup = BeautifulSoup(html,"html.parser")
#以下、処理は同じ
3.最後に
これで無事文字コードエラーも発生せず、htmlから情報を取得することができました。Chromeでの表示を挟むためどうしても処理速度は遅くなってしまいましたが、解決できたので大満足です。
なお、この問題解決のため参照したURLを記載します。大変参考になりました。ありがとうございました。
https://tanuhack.com/selenium/
https://shine-bal.hatenablog.com/entry/2018/08/26/133310
https://www.kumilog.net/entry/unicode-decode-error