BeautifulSoupで「Illegal multibyte sequence」エラーが出る原因と解決方法
BeautifulSoup は、Python で HTML や XML を扱うときによく使われるパーサーライブラリです。
Web ページのスクレイピングや、HTML から特定の要素を抜き出す処理などで頻繁に利用されます。
しかし、日本語などのマルチバイト文字を含むページを扱うと、
Illegal multibyte sequence というエラーが発生することがあります。
このエラーは多くの場合、「文字コードの扱い」が原因です。
この記事では、BeautifulSoup で Illegal multibyte sequence が発生する典型的なパターンと、
その考え方・解決方法をまとめていきます。
BeautifulSoupとは
BeautifulSoup は、Python のライブラリの 1 つで、HTML や XML などのテキストデータを解析し、
DOM ツリーとして扱いやすくしてくれるツールです。タグ名やクラス名、属性などを指定して、
必要な情報だけを取り出すことができます。
Illegal multibyte sequenceエラーとは?
Illegal multibyte sequence エラーは、文字コード関連のエラーの一種です。
日本語や中国語のようなマルチバイト文字を含むテキストを、
間違った文字コードでデコード・変換しようとしたときに発生します。
例えば、本来は Shift_JIS や EUC-JP でエンコードされているデータに対して、
無理やり UTF-8 として解釈しようとすると、バイト列の組み合わせがおかしくなり、
「不正なマルチバイトシーケンス(Illegal multibyte sequence)」と判断されます。
Illegal multibyte sequenceエラーが発生する典型的なコード例
import requests from bs4 import BeautifulSoup url = "http://example.com" res = requests.get(url) soup = BeautifulSoup(res.content, "html.parser") print(soup.prettify())
上記のように、レスポンスボディを res.content(バイト列)で取得し、
そのまま BeautifulSoup に渡しているコードは、一見すると問題なさそうに見えます。
しかし、レスポンスの実際の文字コードと、内部で使用されるデコーダやライブラリの想定する文字コードが食い違うと、
途中の変換処理で Illegal multibyte sequence が発生することがあります。
なぜIllegal multibyte sequenceが発生するのか
このエラーの根本原因は、「バイト列をどの文字コードとして解釈するか」という部分にあります。
- サーバーが返している HTML が Shift_JIS なのに、UTF-8 として扱おうとしている
- レスポンスヘッダの
Content-Typeに書かれている charset と、実際の中身のエンコードが一致していない - 中間で別ライブラリ(iconv など)が変換を行い、その際に不正なバイト列として弾かれる
BeautifulSoup 自体は様々なパーサー(html.parser、lxml、html5lib など)と組み合わせて動作しますが、
どの場合でも、「どのエンコーディングでデコードするか」を間違えると、同様の問題が起こり得ます。
解決パターンの全体像
BeautifulSoup で Illegal multibyte sequence が発生する場合、多くは次のような方針で解決できます。
- レスポンスの文字コードを正しく指定する(
res.encodingを適切に設定するなど)。 - バイト列(
res.content)を自分でデコードしてから BeautifulSoup に渡す。 - 文字コードが不明な場合は、推定ライブラリを使って文字コードを推定したうえでデコードする。
解決方法1:レスポンスの文字コードを正しく設定してから解析する
まずは、レスポンスの文字コードを正しく設定したうえで、res.text を使って解析する方法です。
import requests
from bs4 import BeautifulSoup
url = "http://example.com"
res = requests.get(url)
# サーバーが返すヘッダや中身から推定されたエンコーディングを使用
# サイトによっては "shift_jis" などを明示的に指定した方がよい場合もある
if res.encoding is None or res.encoding == "ISO-8859-1":
res.encoding = res.apparent_encoding
soup = BeautifulSoup(res.text, "html.parser")
print(soup.prettify())
ポイントは、闇雲に "utf-8" を指定するのではなく、
res.encoding や res.apparent_encoding を確認したうえで、
実際のページのエンコーディングに合わせることです。
例えば、対象のページが Shift_JIS で配信されていることが分かっているなら、
以下のように明示的に指定してもかまいません。
res = requests.get(url) res.encoding = "shift_jis" # 実際のページのエンコーディングに合わせる soup = BeautifulSoup(res.text, "html.parser")
この方法は、レスポンスの文字コードが比較的素直なサイトであればシンプルに動作します。
一方で、ヘッダ情報と実際の中身が食い違っているような「クセのあるサイト」では、
後述の方法も併用した方が安全です。
解決方法2:バイト列を自分でdecodeしてからBeautifulSoupに渡す
文字コードを自分でコントロールしたい場合は、
res.content(バイト列)を一度デコードしてから、BeautifulSoup に渡す方法が有効です。
import requests
from bs4 import BeautifulSoup
url = "http://example.com"
res = requests.get(url)
# 本来のエンコーディングが分かっている場合はそれを指定する
html = res.content.decode("utf-8", errors="strict")
soup = BeautifulSoup(html, "html.parser")
print(soup.prettify())
errors="strict" は、デコード時に不正なバイト列があれば例外を投げる設定です。
もし、どうしてもエラーを避けたいケースでは、
いったん "replace" や "ignore" を使うこともありますが、
その場合は文字が欠落・変換される可能性がある点に注意してください。
# 不正なバイト列があっても処理を続けたい場合の例(あくまで最終手段)
html = res.content.decode("utf-8", errors="replace")
soup = BeautifulSoup(html, "html.parser")
重要なのは、「どの文字コードでデコードしているか」を明示的に意識することです。
実際のサイトのエンコーディングに合わせて "shift_jis" や "euc-jp" を指定する場面もあります。
解決方法3:文字コードが不明な場合に推定ライブラリを使う
古いサイトや独自実装のサイトなどでは、レスポンスヘッダと中身が一致していなかったり、
そもそも charset が書かれていなかったりします。
そのような場合、charset-normalizer や chardet といったライブラリで
文字コードを推定してからデコードすると、エラーを避けやすくなります。
import requests from bs4 import BeautifulSoup import charset_normalizer url = "http://example.com" res = requests.get(url) # バイト列からエンコーディングを推定する result = charset_normalizer.from_bytes(res.content).best() html = str(result) # 推定されたエンコーディングでデコードされた文字列 soup = BeautifulSoup(html, "html.parser") print(soup.prettify())
文字コードが混在していたり、ヘッダ情報が信用できないサイトを扱う場合は、
このような推定処理を挟むことで Illegal multibyte sequence の発生を抑えられることがあります。
パーサーの種類とIllegal multibyte sequence
BeautifulSoup は内部処理で、指定したパーサーに HTML を渡して解析します。
よく使われるのは以下の 3 つです。
html.parser(標準ライブラリ)lxmlhtml5lib
Illegal multibyte sequence は、これらのパーサー内部で利用される
文字コード変換処理(iconv など)が、不正なバイト列を受け取ったときにも発生します。
そのため、どのパーサーを使っていても、根本的な対策は「正しい文字コードでデコードする」ことです。
トラブルシューティングのチェックリスト
実際にエラーが出た場合は、次の順番で確認していくと原因を絞り込みやすくなります。
- 対象のサイトの HTML に
<meta charset="...">がどう書かれているか確認する。 - レスポンスヘッダの
Content-Typeにどの charset が指定されているか確認する。 res.encodingとres.apparent_encodingの値を print して比較する。res.textではなくres.contentを使っていないか確認する。- 自分で
decode()する場合、指定しているエンコーディングが正しいか見直す。 - 必要に応じて
charset-normalizerやchardetで推定結果を見てみる。
よくある質問(FAQ)
Q. とりあえず UTF-8 にしておけば問題ありませんか?
A. いいえ。実際のページが UTF-8 であれば問題ありませんが、
Shift_JIS や EUC-JP のサイトを UTF-8 として扱うと、Illegal multibyte sequence を含む
さまざまな文字化けの原因になります。必ず、対象サイトの実際の文字コードを確認してから指定してください。
Q. errors="ignore" や errors="replace" を常に使っても大丈夫ですか?
A. 文字化けや情報欠落が許容できる場面なら使うこともありますが、基本的にはおすすめできません。
デバッグや一時的な回避策として使い、本番コードでは極力、正しいエンコーディングを指定して
正常にデコードできるようにする方が安全です。
Q. どのパーサーを使うとエラーが出にくいですか?
A. パーサーによってエラーメッセージの種類は変わることがありますが、
文字コードが原因のエラーそのものを根本的に避けるには、
どのパーサーを使う場合でも「正しい文字コードでデコードする」ことが重要です。
まずはエンコーディングを見直し、そのうえで用途に応じて
html.parser・lxml・html5lib を選ぶとよいでしょう。
まとめ
BeautifulSoup で Illegal multibyte sequence エラーが出るときは、
ほぼ確実に「文字コードの扱い」に原因があります。
- レスポンスの文字コードを正しく設定する(
res.encodingを見直す)。 res.contentを自分でdecode()し、正しいエンコーディングで文字列にする。- 文字コードが不明な場合は、推定ライブラリでエンコーディングを推定する。
上記のポイントを意識して実装すれば、
マルチバイト文字を含むページでも安定して BeautifulSoup で解析できるようになります。




![[Python]CSVデータをグラフ表示(x軸指定、列指定、範囲指定)](https://machine-learning-skill-up.com/knowledge/wp-content/uploads/2023/11/1-292.jpg)
