FastWQ BeutifulSoup

FastWordQueryでオンライン辞書を新規追加する方法 #

header

どうもこんにちは、パダワンです。今回は、PythonとBeutifulSoupを使って、FastWordQueryでオンライン辞書を新規追加する具体的な方法を紹介、解説していきます。

自己責任の元、用法用量をしっかり守って、活用してください。

また、スクレイピングを完全に解説するとかなり長くなるので、参考記事を各所で紹介しており、足りない部分は参照してください。 やることが多く、知識もある程度必要なので少しハードル高めです。

1. 基本的な考え方 #

FastWordQueryがやってくれることは、基本的にHTMLデータの取得とエンコーディングです。

こちらが用意してあげるのは、そのデータから欲しい情報を抽出して、整形し、そのデータをAnkiのフィールドに出力することです。そのコードを書いたPythonファイルを一つ追加すれば完成です。

基本的には、URLを正しく与えてあげれば、そのWebページのHTMLデータを取得してくれます。何かしらエラーが起きた場合は、データ抽出、整形、出力のどこかでコードのミスがあります。

私が確認したところ、いくつかうまくいかないメソッドがあったので、うまくいくパターンをまじえてそれらを紹介します。

まず、最初からPythonファイルを書いて、実験しているとどこでうまくいったか、失敗したかわからないので、まずはターミナルを使ってコードがうまくいくか小さくテストしながらコードを考えます。

【超初心者向け】ターミナルとは?Macの便利ツールを徹底解説 | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト

2. スクレイピングの工程 #

10分で理解する Beautiful Soup - Qiita

スクレイピングでは、サイトにアクセスして、構文を解析して、という流れが必要になってきます。

まとめると、このような4つの工程になります。 1. スクレイピング対象のURLにリクエストを送り、HTMLを取得する 2. パーサを指定してHTML文字列を元にBeautifulSoupオブジェクトを生成する 3. BeautifulSoupオブジェクトから必要となるデータを抽出する 4. 抽出したデータを望む形に整形し、特定のフォーマットで出力する

重要なのは3と4の工程です。スクレイピングでは手に入れたデータから必要な情報のみ抽出し、きれいな形で出力します。

3. テスト環境の構築 #

3-1. テキストエディタの導入 #

テキストエディタが無いと流石にpythonファイルを書くのはきついのでAtomかVScodeあたりのエディタをPCにインストールしてください。

A hackable text editor for the 21st Century

テキストエディタも様々な拡張機能があるので、カスタマイズをするとかなり便利なので、興味がでたら色々調べてみてください。

【すぐわかる!】Atomエディタのインストールと日本語化、便利な使い方

3-2. Pythonとpipのインストール #

ターミナルでスクレイピングをできるようにするため、まずは、PCにPythonとpipをインストールします。

pipはPythonのパッケージ管理をするツールです。

python3系を入れれば、pipも同時にインストールされるようになっているらしいです。

Welcome to Python.org

Pythonのパッケージ管理システムpipの使い方 | note.nkmk.me

3-3. Python基礎(モジュール、ライブラリ、パッケージ) #

まず、Pythonファイルを扱う上で必要な知識として

  • モジュール
  • パッケージ
  • ライブラリ を説明します。
名称説明
モジュールPythonでは関数やクラスなどをまとめて書いたファイルをモジュールというre(正規表現モジュール)、os(OSに依存した機能を利用したモジュール
パッケージモジュールと__init__.pyを含むディレクトリをパッケージ(Regular packages)という。bs4(BeautifulSoup系列のパッケージ)
ライブラリライブラリとは、いくつかのパッケージをまとめて一つのライブラリとしてインストールできるようにしたもの。パッケージの集合として考えれば良い。Requests(HTTP通信ライブラリ)、NumPy(科学技術計算パッケージ)、Matplotlib(グラフ描写パッケージ)

スクレイピングなどでは、Pythonにもともと組み込まれている機能以外の必要な機能を組み込んだ外部ファイルをうまく利用しています。

3-4. スクレイピングをテストするのに必要なライブラリをインストール #

スクレイピングをターミナルでテストする前に2つインストールする必要のあるPythonライブラリがあります。

スクレイピングの4つの工程 1. スクレイピング対象のURLにリクエストを送り、HTMLを取得する 2. パーサを指定してHTML文字列を元にBeautifulSoupオブジェクトを生成する 3. BeautifulSoupオブジェクトから必要となるデータを抽出する 4. 抽出したデータを望む形に整形し、特定のフォーマットで出力する

Requestsは(1)のHTTP通信をしデータをフェッチするために利用します。 BeautifulSoupは主に(2,3)フェッチしたデータを解析し、情報を抽出するために利用します。

FastWordQueryでは、実際は、Requestsではなくurllib2を利用していますが、テストするのは主に4つの工程の内(3,4)なので、あまり問題ではありません。代用として使いやすいRequestsを使用します。

urllib2 は URLs (Uniform Resource Locators) を取得するための Python モジュールです。このモジュールはとても簡単なインターフェースを urlopen 関数の形式で提供しています。また、このモジュールは一般的な状況で利用するためにいくらか複雑なインターフェースも提供しています - basic 認証やクッキー、プロキシ等。これらは handler や opener と呼ばれるオブジェクトとして提供されます。出典 : urllib2 を使ってインターネット上のリソースを取得するには — Python 2.7.18rc1 ドキュメント

ライブラリのインストール 以上の2つのライブラリをPCにpipを使ってインストールします。

$ pip install beautifulsoup4
$ pip install requests

【Python入門】pipとは?使い方をわかりやすく解説! | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト

3-5. ターミナルの基本的な使い方とコマンド #

ターミナルを使うには、ある程度、コマンドの知識が必要になります。

コマンドリスト機能
cdディレクトリ移動
lsディレクトリのリスト表示
mkdirディレクトリ作成
touch空ファイル作成
exitターミナルの終了

Macのターミナルコマンド一覧(基本編) - Qiita

細かいコマンドはこのサイトでまとまっています。 UNIXコマンド集

4. スクレイピングの基礎 #

ターミナルでのコードテストの工程はだいたい次のようになります。

  1. ターミナルで適当な作業ディレクトリまで移動
  2. pythonの起動
  3. モジュールのインポート
  4. URLを与えて対象ページにアクセスしてデータの取得
  5. エンコーディング
  6. 取得データをパース(解析)してbsオブジェクトを生成
  7. bsオブジェクトからデータを抽出
  8. 無駄なタグを削除したりして、データを整形
  9. 出力してみて確認

最終的に出力してみて欲しい形になっていたら成功です。 うまくいかない場合は、基本的に工程の7以降で出現します。 最初にターミナルで小さくテストして最終的にFastWrodQueryでうまくいけばよいです。 FastWordQueryで書くpythonファイルでは、主に7~9の工程です。

4-1. 工程(1~2) ターミナルで適当な作業ディレクトリまで移動 #

ターミナルを開いて、適当なディレクトリを作成して、移動します。

$ mkdir Scraping 
#新規ディレクトリを作成

$ cd Scraping
#ディレクトリまで移動

$ python3
#python3を起動

pythonコマンドでpythonを起動したら、pythonのコマンドを入力できるようになります。

pythonの終了はquit()でできます。

ターミナル

terminal_use_OK

こんな感じです。

4-2. 工程(3~6) 対象ページにアクセスする #

参考(1)

10分で理解する Beautiful Soup - Qiita

参考(2)

Pythonのbeautifulsoupでスクレイピングをしてみよう | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト

>>> import requests 
#requesetsモジュールのインポート

>>> url = "https://www.hogehoge.com/" 
#アクセス先のURLを変数として与える

>>> response = requests.get(url) 
#HTMLデータの取得
>>> response.encoding = response.apparent_encoding
#エンコーディング(文字化け防止) 

>>> print(response.text)
# 出力

4-3. 工程(3~9) 実際にスクレイピングしてみる #

#モジュールのインポート
>>> import requests
#bs4ライブラリからBeautifulSoupモジュールをインポート
>>> from bs4 import BeautifulSoup 

>>> url = "https://www.hogehoge.com/" 
#アクセス先のURLを変数として与える

#HTMLデータの取得とエンコーディング 
>>> response = requests.get(url)
>>> response.encoding = response.apparent_encoding

#取得データをテキストとしてBeautifulSoupに渡しHTMLを解析(パース) 
>>> bs = BeautifulSoup(response.text, 'html.parser')

#bsオブジェクトからpタグ出力 
>>> for i in bs.select("p"):
        print(i.getText())

4-4. BeautifulSoup #

4-4-1. BeautifulSoupとは #

Beautiful Soup はHTMLやXMLファイルからデータを取得するPythonのライブラリです。あなたの好きなパーサー(構文解析器)を使って、パースツリー(構文木)の探索、検索、修正を行います。 これはプログラマーの作業時間を大幅に短縮してくれます。 出典: kondou.com - Beautiful Soup 4.2.0 Doc. 日本語訳 (2013-11-19最終更新)

4-4-2. find(),find_all() #

BeautifulSoupの基本のメソッドです。

HTTP通信で取得したデータを解析してbsオブジェクトを生成したら、そのオブジェクトからfind()とfind_all()を使用して要素を抽出します。

例です。

soup = BeautifulSoup(response.text, 'html.parser')
# bsオブジェクトの生成

el = soup.find('p')
# bsオブジェクトからdiv要素を抜き出す
# HTMLテキスト内から、頭から一番最初のp要素を抜き出す。

クラスが複数の要素に使われており、重複があってもfind()で最初の一つを抜き出せます。戻り値としてbsオブジェクトを返します

find_all()は複数のタグを取得することができます。ただし、戻り値としてリストを返します

一度で必要な情報を抜き出せなくても、find()やfind_all()を複数回繰り返し、HTMLの階層構造をたどることで、うまく絞り込むことが可能になります。

参考(1) : 【Python】BeautifulSoupのfindとfind_allの使い方【スクレイピング】 - narupoのブログ

参考(2) : 【Python】BeautifulSoupでclassを指定して要素を取得する方法【スクレイピング】 - narupoのブログ

基本的な使用例をいくつかあげます。

soup.find('p', class_='hoge')
# hogeクラスを持つpタグを検索
soup.find('p', id='fuga')
# manidを持つpタグを検索
soup.find('p', class_='hoge fuga') # AND検索
soup.find('p', class_=['hoge',' fuga'])
# OR検索

soup.find('p', attrs={'class': 'hoge'})
# 引数attrsを使って属性で検索する classで検索
soup.find(attrs={'id': 'link'})
# idで検索
soup.find(attrs={'class': 'hoge', 'id': 'link'})
# AND検索

classはpythonの予約語なので、attrsを使う時以外は末尾にアンダーバーをつけてclass_としてください。

更に詳しく知りたい場合は次の記事を参考にしてください。

図解!Python BeautifulSoupの使い方を徹底解説!(select、find、find_all、インストール、スクレイピングなど)

4-4-3. select(),select_all(),wrap() #

私が実験した結果、いくつかうまくいかないメソッドがありました。

  • select()
  • select_all()
  • wrap()

検証が足りていないのでこれらのメソッドを使ってうまくいく可能性もありますが、面倒くさいので(笑)、うまくいったパターンのみ紹介します。

select()やselect_all()はfind()、find_all()の代わりとすることができるメソッドですが、基本的にfind()とfind_all()を利用します。

wrap()は最終的に抽出したデータをタグで囲み、cssでデザインしやすくするのに使えますが、別のメソッドを利用します。

4-5. 検証ツールで要素の確認 #

とりあえずBeautifulSoupの公式ドキュメントである、このサイトにブラウザでアクセスしてみてください。

Beautiful Soup Documentation — Beautiful Soup 4.9.0 documentation

ブラウザの検証ツールを使い、HTMLのソースから抽出したい情報のクラスやidなどを確認してください。

検証ツール(デベロッパーモード)の使い方

オンライン辞書からスクレイピングするためには、うまく要素情報を取得できるように、どのクラスやidを選ぶかが重要になります。

AnkiでHTMLやCSSを書いてわかると思いますが、オンライン辞書はHTMLでうまく構造化、CSSでデザインされているので、用意にクラスやidがわかります。

抽出したい情報が抜き出しづらい場合は、find()等でその親要素を抜き出してから、抜き出したい子要素に対して、再びfind()で抽出します。

どこで抜き出すべきかは、ターミナルでテストしてみるのをおすすめします。

ためしにBeautifulSoupの公式ドキュメントのサイトのソースを見てみます。

devtool

こんな感じで要素を確認してください。

4-6. 抽出したデータを整形する #

4-6-1. データ型の変更と確認 #

データ整形時に気を付けることは、データの型です。

図解!Python データ型を徹底解説!(確認・変換・指定方法と種類一覧)

bsオブジェクトをまず生成するわけですが、整形の途中で型を変更したり、出力する際にも変更を施します。

ターミナルではテストする際に逐一、データの型を確認してみてください。

type(object)で確認できます。

4-6-2. 不必要なタグやテキストの除去 : decompose()とextract() #

参考 :

BeautifulSoup で style タグ、script タグ、style 属性を除去する

decompose()を使えば抽出後のデータから不必要なタグを除去することができます。どの段階で、除去するかは任意です。find()等である程度まで絞り込んでから除去すればよいです。

  • styleタグ
  • scriptタグ
  • audioタグ
  • コメント

上のようなタグを除去します。ターミナルでテストしたときに紛れ込んでいるタグを発見したら除去コードを入れます。なさそうであれば、いりません。

4-6-3. decompose() と for文 : タグと挟んでいる内容を完全削除する #

decompose()とfor文を組み合わせて、タグと囲まれているテキストを完全に除去できます。

コメントを削除する際は、bs4パッケージからCommentモジュールをインポートする必要があります。

from bs4 import BeautifulSoup, Comment

html = """
<html>
 <head>
   <title>タイトル</title>
   <script type="text/javascript"><![CDATA[
   alert('hoge');
   ]]>
   </script>
 </head>
 <body>
   <!-- コメント -->
   <h1>見出し</h1>
   <div>本文</div>
 </body>
</html>
"""

soup = BeautifulSoup(html, "lxml")

# コメントタグの除去
for comment in soup(text=lambda x: isinstance(x, Comment)):
    comment.extract()

# scriptタグの除去
for script in soup.find_all('script', src=False):
    script.decompose()

# テキストだけの抽出
for text in soup.find_all(text=True):
    if text.strip():
        print(text)

リスト内包表記で一文で書くことできます。次のコードは同じ処理です。

for comment in soup(text=lambda x: isinstance(x, Comment)):
    comment.extract()

[s.extract() for s in soup(text=lambda x: isinstance(x, Comment))]
[s.decompose() for s in soup('style')]
[s.decompose() for s in soup('audio')]
[s.extract() for s in soup(text=lambda x: isinstance(x, Comment))]

4-6-4. タグを開放する : unwrap() #

タグを削除するのではなく、外すことによって中のテキストを残すことができます。 これもfor文を利用します。

## aタグをはずして中のテキストのみ残す    
for element in soup.find_all('a'):
    element.unwrap()    

4-6-5. タグを変更する : bsObj.name #

私はpタグをなるべく使いたくないのでdivタグにすべて変更します。 for文とfind_all()を利用します。 bsObj.nameでタグ名称にアクセスし、変更できます。

## Tagモジュールをインポート必要あり
from bs4 import Tag

## pタグをdivタグに変更する
for element in soup.find_all('p'):
    element.name = 'div'

4-6-6. タグで出力データをラップする : insert(),append() #

wrap()の代わりに2つのメソッドを利用してデータをラップします。

  • obj.append() : 配列最終の要素に挿入するのに利用
  • obj.insert() : 配列最初の要素に挿入するのに利用する

FastWrodQueryでAnkiカードのフィールドに出力するときに最終的にstr型データで出力します。

その出力前にbsオブジェクトからリストにする工程があります。

まとめると、bsオブジェクトからリスト内にHTMLタグとコンテンツを格納した後で、strとして結合して一つのデータとして出力します。

bsオブジェクトに対してwarp()を使ってもうまくいかないので、リストにした後でそのリストにhtmlタグを最初と最後に挿入するという形でラップをします。

5. Pythonファイルを書く #

5-0. ディレクトリ構造 #

実際にpythonファイルを書きます。次のディレクトリまでアクセスしてください。

/Users/UserName/Library/Application Support/Anki2/addon21/1807206748/service/dict

このディレクトリに各種辞書のpythonで書かれたスクレイピング用のファイルが存在しています。vocabulary.pyが最も分かりやすいので参考にしてください。次に紹介する例のpythonスクリプトもそこから改造しています。

例えば、“webdict2020"という名前のオンライン辞書があったとして、webdict2020.pyを作成します。テキストエディタで新規作成して、このディレクトリに保存してください。

5-1. Pythonファイルの構造 #

FastWordQueryで自分で書くpythonファイルの構造の次の項目がすべてです。

  1. 必要モジュールのインポート
  2. クラスの定義
    1. 初期化
    2. メインの処理(データ取得、解析、抽出、整形)定義
    3. 出力処理の定義

最小の構成で書いた例です。

# -*- coding:utf-8 -*-
from ..base import *  
# ワイルドカード (*)を使うとすべてのオブジェクトがインポートされる
# 上位ディレクトリ service/ のbaseモジュール(ファイル base.py)のすべてのオブジェクト(関数、変数、クラスなど)をすべてインポートする

from bs4 import BeautifulSoup, Tag, Comment
# bs4パッケージからモジュールを指定してインポート

@register([u'辞書名称',u'OnlineDictionaryName'])
class DictionaryName(WebService):

    # 初期化
    def __init__(self):
        super(DictionaryName, self).__init__()

    # メインの処理(データ取得、解析、整形)
    def _get_from_api(self):
        data = self.get_response(u"https://hogehoge/fugafuga/{}".format(self.quote_word))
        # get_responseはbase.pyで定義されたメソッド
        # urllib2.request もしくは urllib.request を使用
        soup = parse_html(data)
        # base.pyで soup = BeautifulSoup(html, 'html.parser') を返している感じ(大体)
        result = {
            'summaryJP': u'',
            'definition': u'',
            'reibun': u'',
        }
        # 出力用のキャッシュとして保存する

        # 出力データ
        result_jap = soup.find('div', class_='AAAAA')
        # 要素が存在した場合のみ処理
        if result_jap: 
            list_jap = result_jap.contents #bsオブジェクトをリストに
            # リストに挿入しラップ
            list_jap.insert(0,'

<div id="wrapper_AAAAA">') list_jap.append('</div>

')
            # strとして結合して保存
            result['summaryJP'] = u''.join(str(e) for e in list_jap)
            # contents は list
            # u'' は str 
            # join()はstr型のメソッド                          

        return self.cache_this(result)

    # 出力処理    
    @export('MeaningJAP')
    def fld_summaryJP(self):
        return self._get_field('summaryJP')

これで出力されるデータは <div id="wrapper__xxx">抽出データ</div> という形になるので、cssでデザインする際に#warpper__xxxを利用することで、デザインしやすく、Ankiのcssテンプレートに干渉されずらいデータを作成できます。

pythonファイルのbase.pyにだいたい必要なパッケージやモジュールのインポート文が書かれており、またメソッド等が定義されており、そこから参照して新しいクラスを作成しています。

必要なモジュールが他にあれば明示的にインポートしてください。

Python, importの使い方(from, as, PEP8の推奨スタイル, 注意点など) | note.nkmk.me

5-2. FastWordQueryでテスト #

これは実際に書くpythonファイルの例です。 今までの情報をだいたいつめこんであります。うまく書き換えたりして利用してください。

  • DictionaryName : 書き換え
  • URL (https://hogehoge/fugafuga/) : 書き換え
  • find()で利用している各種クラスやid : 書き換え
# -*- coding:utf-8 -*-
from ..base import *  
# ワイルドカード (*)を使うとすべてのオブジェクトがインポートされる
# 上位ディレクトリ service/ のbaseモジュール(ファイル base.py)のすべてのオブジェクト(関数、変数、クラスなど)をすべてインポートする

from bs4 import BeautifulSoup, Tag, Comment
# bs4パッケージからモジュールを指定してインポート

@register([u'辞書名称',u'OnlineDictionaryName'])
class DictionaryName(WebService):
    # base.pyで定義されたクラスWebService オブジェクトを引数にしてクラス定義

    def __init__(self):
        super(DictionaryName, self).__init__()

    # メインの処理(データ取得、解析、整形)
    def _get_from_api(self):
        data = self.get_response(u"https://hogehoge/fugafuga/{}".format(self.quote_word))
        # get_responseはbase.pyで定義されたメソッド
        # urllib2.request もしくは urllib.request を使用
        soup = parse_html(data)
        # base.pyで soup = BeautifulSoup(html, 'html.parser') を返している感じ(大体)
        result = {
            'summaryJP': u'',
            'definition': u'',
            'reibun': u'',
        }
        # 出力用のキャッシュとして保存する

        # 出力データ(1) : 共通の最小の構成
        result_jap = soup.find('div', class_='AAAAA')
        # 要素が存在した場合のみ処理
        if result_jap: 
            list_jap = result_jap.contents #bsオブジェクトをリストに
            # リストに挿入しラップ
            list_jap.insert(0,'<div id="wrapper_AAAAA">') 
            list_jap.append('</div>')
            # strとして結合して保存
            result['summaryJP'] = u''.join(str(e) for e in list_jap)
            # contents は list
            # u'' は str 
            # join()はstr型のメソッド                          

        # 出力データ(2) 
        result_ken = soup.find('div', class_='XXXXX').find('div', class_='YYYYY')
        if result_ken:  
            [s.extract() for s in result_ken(text=lambda x: isinstance(x, Comment))]
            [s.decompose() for s in result_ken('b')]
            [s.decompose() for s in result_ken('audio')]
            for tagname_ken in result_ken.find_all('p'):
                tagname_ken.name = 'div'
            for alink_ken in result_ken.find_all('a'):
                alink_ken.unwrap()    
            list_ken = result_ken.contents
            list_ken.insert(0,'<div id="wrapper_YYYYY">')
            list_ken.append('</div>')
            result['definition'] = u''.join(str(e) for e in list_ken)    

        # 出力データ(3) : 解説
        list_rei = soup.find_all('div', class_='BBBBB')
        # リストの最後の要素に関して抜きしたいのでインデックスで-1を指定
        result_rei = list_rei[-1]
        if result_rei:
            #タグの除去
            [s.decompose() for s in result_rei('audio')]
            # テキスト(例.あいうえお)を検索して削除
            if result_rei.find(text='あいうえお'):
                result_rei.find(text='あいうえお').extract()
            # pタグをdivに変更
            for tagname_rei in result_rei.find_all('p'):
                tagname_rei.name = 'div'
            # aタグを開放
            for alink_rei in result_rei.find_all('a'):
                alink_rei.unwrap()
            list_rei = result_rei.contents
            list_rei.insert(0,'<div id="wrapper_BBBBB">')
            list_rei.append('</div>')
            result['reibun'] = u''.join(str(e) for e in list_rei)   

        return self.cache_this(result)

    # 出力処理    
    # 出力データ(1) 
    @export('MeaningJAP')
    def fld_summaryJP(self):
        return self._get_field('summaryJP')
        # base.py で定義されたメソッド _get_field() 
        # 辞書から key で値を返すことによって出力している

    # 出力データ(2)    
    @export([u'語義', u'definition'])
    @with_styles(cssfile='_dictionaryname.css')
    def fld_definition(self):
        return self._get_field('definition')

    # 出力データ(3)    
    @export([u'例文', u'Reibun'])
    @with_styles(cssfile='_dictionaryname.css')
    def fld_reibun(self):
        return self._get_field('reibun')

5-3. うまくいかないパターン #

ファイル名とかメソッド名とか結構重要で、どこかで重複とかしているとエラーになったり、うまく出力できない場合があるので、重複しないように名前をつけてください。

他の辞書のpythonファイル等をよくみて名前をつけてください。

  • pythonファイル名称 : オンライン辞書名称(小文字).py
  • cssファイル名称 : _オンライン辞書名称(小文字).css
  • クラス名称 : オンライン辞書名称(頭大文字)

出力処理定義 : この部分も重複しないようにしてください。

result = {
    'yyyyyyyy': u'',
}

@export([u'題名', u'definition'])
@with_styles(cssfile='_dictionaryname.css')
def fld_yyyyyyyy(self):
    return self._get_field('yyyyyyyy')

5-4. try-except : 例外処理 #

ターミナルでうまく行っても実際にアドオンでスクリプトを起動させるとうまくいかない場合があります。そんなとき、どこでうまくいかないのか確認する方法があります。

といってもかなり原始的というか、面倒くさい方法なのですが。

とりあえずうまく行ってないだろう部分に宛をつけて、その部分だけ例外処理を施します。

うまくいかない部分は、大抵は抽出、整形、出力のどこかです。ターミナルでテストしていた場合に失敗する可能性が高いのは、抽出か整形のところです。

もしくは、データの型が一致してないとかですね。

参考 : Pythonの例外処理!try-exceptをわかりやすく解説! | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト

文法的に正しいコードを書いても、実行時にエラーが発生することがあります。これが「例外」というものです。英語ではExceptionといい、よく出てくるキーワードなので覚えておきましょう。

5-4-1. 例外処理の基本 #

try-except文を使用し「例外処理」を行います。

try: 
    #例外発生する可能性のある処理内容
except エラー名:
    #例外発生時に行う処理

まずは、tryの中に指定された処理が実行されます。例外が発生しなければ、except文はスキップされます。

しかし、try実行中にexceptキーワードの後に指定したエラー名と一致する例外が発生すると、except文が実行され、それ以降に記述された処理も実行されます。

このように「except」ブロックで例外を捉えて処理をすることで、エラー発生箇所で処理が停止することはありません。

5-4-2. pass文 : 何もしない #

exceptで特に行う処理がなければpassを書いておけばいよいです。

try:
    fugafuga #例外発生する可能性のある処理内容
except:
    pass # 例外が発生しても何もしない

5-4-3. 例外処理を実際に書く #

例えば、上で紹介したうまくいかないメソッドwrap() は、その要素を引数で指定したタグを挟みます。 新しく挟まれたものを返します

soup = BeautifulSoup("<p>I wish I was bold.</p>")
soup.p.string.wrap(soup.new_tag("b"))
# <b>I wish I was bold.</b>

soup.p.wrap(soup.new_tag("div"))
# <div><p><b>I wish I was bold.</b></p></div>

整形処理の部分に施す例です。

result_jap = soup.find('div', class_='AAAAA')
        if result_jap: 
            try:
                result_jap.wrap(result_jap.new_tag('div',class_="wrapper_method"))
                list_jap = result_jap.contents
            except:    
                list_jap = result_jap.contents
                list_jap.insert(0,'<div id="wrapper_list">') 
                list_jap.append('</div>')

            result['summaryJP'] = u''.join(str(e) for e in list_jap) 

tryもexceptのどちらもラップ処理を施しています。 wrap()がうまくいけば出力されたデータは、wrapper_methodidが、 wrap()がうまくいかなければ出力されたデータは、wrapper_listidがdivに書き込まれているはずです。

こういう面倒くさいことを何回かためすと問題がどこにあるか確認でき、解決できます。

6. CSSファイルでデコレーション #

おさらいです。出力のコードに @with_styles(cssfile='_dictionaryname.css')を付けることによってカードのフィールドに出力するさいに<link type="text/css" rel="stylesheet" href="_dictionaryname.css">を付加することができます。

_dictionaryname.css は頭のアンダーバーはそのままで、作成したPythonファイルの名称をつけてあげてください。

@export([u'日本語名称', u'definition'])
@with_styles(cssfile='_dictionaryname.css') 
def fld_definition(self):
    return self._get_field('definition')

@decoratorはデコレーターで関数の修飾(処理の付加)を行います。 上位階層のディレクトリにあるbasy.pyで定義された、export()with_styles()の処理の付加を行っています。with_styles()でフィールドに出力する際にcssファイルを付加できます。

Pythonのデコレータについて - Qiita

また、cssファイルはスクレイピングする際にstaticフォルダからmediaコレクションのフォルダにコピーされ、カードのフィールドからmediaコレクションのフォルダにあるcssファイルが参照されることによって、最終的にレンダリングされます。

この際に、気をつけることは、cssの表現の優先度です。 これは、過去の記事でも書きましたがcssでは重複するデザインに優先度があり、より優先的なものが最終的に表現されます。

↓ 過去記事参照 フォントと文字装飾にまつわるお話 - アンキヨリハジメヨ

大きな枠での優先度を考えると、Ankiの場合は4つほど考えられます。結構複雑そうなので、自分もあまり調べてないです。

  1. フィールド内部のCSSやスタイル(フィールドデータに直書き)
  2. HTMLテンプレートのインライン記述(タグにスタイルの直書き)
  3. CSSテンプレートの記述欄(基本)
  4. 外部css<link>

基本的には、要素に直で書いたスタイルが優先されます。

まあ、この辺は本筋とあまり関係ないので、実際に自分で調べてみてください。 とにかく、うまく表現されない場合は、このような優先度があり、競合した結果としてうまくいってないはずです。

↓参考記事

知らないとハマる!CSSを書く場所と読込順とインライン記述の優先関係

HTMLテンプレートに<link>を書くとか、CSSテンプレートに<link>の代わりとなるスタイルを書くとかでもいいですが、<link>をフィールドに出力することによって、カード編集の際に、PC版でもMoblie版でもフィールド編集の項で実際にレンダリングしてくれるのがメリットとなります。もちろんその上に更にデザインが重なることによって最終的な表現が変更される可能性もあります。

cssファイルでの表現は、Atomとかテキストエディタを使って、レンダリングをテストした上で書くと時間を節約できます。

適当なデザイン用のcssファイル_dictionaryname.cssが完成したら、次のディレクトリまで行ってcssファイルをコピー・ペーストしてください。

/Users/UserName/Library/Application Support/Anki2/addon21/1807206748/service/static

FastWordQueryで適当な単語を一つスクレイピングしてみてどのようになるか確認したら、すべての工程が終了です。

お疲れ様でした。

7. おわり #

いかがでしたでしょうか。

  • エディタ導入
  • ライブラリ導入
  • ターミナル使用

ここからまず結構、ハードル高めですね。 実際のオンライン辞書をスクレイピングするファイルを直接書くのはまずいので、方法だけ記述する記事を書いてみました。

うまくいかない場合は、ネットで色々しらべればわかるはずです。実験も色々やってみてください。

またしつこいですが、利用の際は、サイト規約を守り、一度に大量にやりすぎない、時間を開けて行う等、しっかりと用量用法を守って、自己責任の元で利用してください。

では、楽しいAnkiライフを。 それでは、また。