0
1

More than 3 years have passed since last update.

ゆっくりに青空文庫を読んでもらう: コード改善編

Last updated at Posted at 2020-09-02

前回はゆっくり(SofTalk)に辞書登録しつつ読み上げさせることに成功しました
今回は、勉強のために公式ドキュメントや関連する記事を読みつつ、前回のコードを改善していきます

図書カードからURL取得まで

改善したコード

aozora_urlsplit.py
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin

def aozoraurl(base_url):
    page = requests.get(base_url)
    soup = BeautifulSoup(page.text, 'lxml')

    xhtml_relative_url = soup.select('table.download a[href*=".html"]')[0].get('href')
    zipdl_relative_url = soup.select('table.download a[href*=".zip"]')[0].get('href')

    xhtml_url = urljoin(base_url, xhtml_relative_url)
    zipdl_url = urljoin(base_url, zipdl_relative_url)

    return xhtml_url, zipdl_url

if __name__ == '__main__':
    print(aozoraurl("https://www.aozora.gr.jp/cards/000879/card85.html"))

Beautiful Soup 4

Beautiful Soup はHTMLやXMLファイルからデータを取得するPythonのライブラリです。
Beautiful Soup 4.2.0 Doc. 日本語訳より

BeautifulSoup コンストラクタにHTMLドキュメントを渡すことで、その構造を解析します

from bs4 import BeautifulSoup
soup = BeautifulSoup(page.content, 'lxml')

'lxml' はパーサ(構文解析器)を指定しています
HTMLパーサはPython標準(Batteries included)の html,parser , 高速な lxml , 非常に寛大な html5lib から選択できます
公式ドキュメントでは lxml が推奨されているようです

If you can, I recommend you install and use lxml for speed.
Beautiful Soup 4.9.0 documentation より

Requests

Requestsは、人が使いやすいように設計されていて、Pythonで書かれている Apache2 Licensed ベースのHTTPライブラリです。
Requests: 人間のためのHTTP より

requests.get() にURLを渡すことで、Response オブジェクトが返されます
Response の中身を取得するのには、.text.content が使えます

公式ドキュメントより
import requests
r = request.get(URL)
r.content # バイナリデータを取得
r.text    # テキストデータを取得

……バイナリとテキストの違いがわからないのですが、ここ1によるとHTMLやXMLには.text、画像やPDFには.contentだそうです

2種類のURL (XHTML / zip)取得

要素取得の方法は色々ありますが、CSSセレクターが一番汎用性があると思います。

If you want to search for tags that match two or more CSS classes, you should use a CSS selector:

公式ドキュメントより
css_soup.select("p.strikeout.body") 
# [<p class="body strikeout"></p>]

青空文庫図書カードのページからそれぞれのURLを取り出します
soup.select の返り値はlistであることに注意しましょう

xhtml_url = soup.select('table.download a[href*=".html"]')[0].get('href')
zip_url   = soup.select('table.download a[href*=".zip"]')[0].get('href')

urljoin

青空文庫図書カードのページから、それぞれのページへのリンクは相対リンクで記載されています
urljoin を用いて元のURLと合成し、絶対リンクに戻します

urllib.parse.urljoin(base, url, allow_fragments=True)
"基底 URL"(base)と別のURL(url)を組み合わせて、完全な URL ("絶対 URL") を構成します。
urllib.parse --- URL を解析して構成要素にする より

公式ドキュメントより
>>> from urllib.parse import urljoin
>>> urljoin('http://www.cwi.nl/%7Eguido/Python.html', 'FAQ.html')
'http://www.cwi.nl/%7Eguido/FAQ.html'

return a, b

複数の返り値(今回はURL2つ)を返したいときは、このように書くのが最も簡潔なようです

Pythonでは、単純にカンマ区切りでreturnするだけで文字列だろうが数値だろうがまとめて返せる。
Pythonの関数で複数の戻り値を返す方法 | note.nkmk.me より

この場合返り値はタプルになります。

XHTMLページからのルビ抽出まで

改善したコード

aozora_rubylist.py
from bs4 import BeautifulSoup
import requests
import jaconv

def aozoraruby(xhtml_url):
    page = requests.get(xhtml_url)
    soup = BeautifulSoup(page.content, 'lxml')

    _ruby = soup.select("ruby")

    _rubylist = [i.get_text().replace(")", "").split("(") for i in _ruby]

    for index, item in enumerate(_rubylist):
        _rubylist[index][1] = jaconv.kata2hira(item[1])

    return _rubylist

if __name__ == '__main__':
    print(aozoraruby("https://www.aozora.gr.jp/cards/000119/files/624_14544.html"))

ルビの抽出

青空文庫XHTML本文からrubyタグ以下の文字を抽出します
.select("ruby") が一番簡単だと思います

とりあえず取得した内容を確認します
.get_text() で文字列部分を抽出できますが、リストには適用できないのでfor文で回します

_ruby = soup.select("ruby")
for i in _ruby:
    print(i.get_text())

# 重(かさ)
# 死骸(しがい)
# 麻利耶(マリヤ)
# ︙

漢字(ルビ) の形で出力されます

ルビのリスト作成

取得したルビをリストにします。
単純な ["漢字(ルビ)", "漢字(ルビ)", ...] のリストでは使いづらいので、[["漢字", "よみがな"], ["漢字", "よみがな"], ...]の二次元配列を作成します

for文でリストを作成する際、これまではfor文の前で空リストを作成してそこに要素を追加していました
しかし、リスト内包表記という方法を使えば一文で作成できるようです 2 3

x = [i.get_text().replace(")", "").split("(") for i in _ruby]
# [['重', 'かさ'], ['死骸', 'しがい'], ['麻利耶', 'マリヤ'],...]

カタカタをひらがなに

SofTalkに辞書登録するために、読み仮名がカタカナのものをひらがなにします
こちらのQiita記事によると、今回の用法ではjaconvが適しているようです

jaconv (Japanese Converter) はひらがな・カタカナ・全角・半角の文字種変換を高速に行います。 Pythonのみで実装されているので、Cコンパイラが使えない環境でも利用できます。
jaconv/README_JP.rst at master · ikegami-yukino/jaconv · GitHub より

for index, item in enumerate(_rubylist):
    _rubylist[index][1] = jaconv.kata2hira(item[1])
# [['重', 'かさ'], ['死骸', 'しがい'], ['麻利耶', 'まりや'],...]

  1. What is the difference between 'content' and 'text'
    https://stackoverflow.com/questions/17011357/what-is-the-difference-between-content-and-text 

  2. 内包表記を使ってリストの作成を簡潔に書く - Qiita
    https://qiita.com/tag1216/items/040e482f9844805bce7f 

  3. リスト内包表記の使い方 | note.nkmk.me
    https://note.nkmk.me/python-list-comprehension/ 

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1