requests+pyqueryで気をつけること

requests+pyqueryで文字列の扱いに悩まされるのは主に二箇所ある。そしてその組み合わせのエラーが出るので悩ましい。

一点目はrequestsでの文字化け

requestsでcontentとtextの違いはtextはr.encodingでdecodeされたunicode文字列だということだ。なので正しくデコードされたunicode文字列を推定できればrequestsでの文字化け問題は解決する

だが常にunicode文字列をpyqueryに渡せばOKかというとそうでもない。

Unicode文字列をpyqueryに渡すと起きるエラー(Unicode strings with encoding declaration are not supported.)

常にUnicode文字列を渡すようにすると Unicode strings with encoding declaration are not supported. というエラーが散見されるようになる。これはpyqueryが内部的に使っているlxmlがヘッダーにエンコーディングが指定しているHTMLを渡すとそれにあわせてデコードしようとするらしく、既にユニコード文字列化されたHTMLもその作法にしたがってエラーになるというものだ。

というわけでこの場合はもとのstr文字列を渡さないといけないらしい。

これが結構面倒くさい。requestsでヘッダーにエンコーディングが指定されているかどうか知るオプションあるんかね?

PyQueryの挙動がちょっとわからん

企業のプレスリリースをテキストマイニングしようと思うと色々大変なわけだ。そもそもプレスリリースの媒体がpdfを想定されているために、タイトルのみ本文無しという潔いRSSが多いしそういうのは購読しても嬉しくない。

というわけでプレスリリース一覧のページからpdfのURLを抜き出してpdfminerかましてデータ抽出やらないといけないしそっちのほうが逆に構造化されていてスクレイプしやすかったりする。

で、PyQueryを使ってガリガリやっていたのだが、何故かスクレイプできないサイトがあった。BeautifulSoupではちゃんとスクレイピング出来たのでエラーの原因が気になって調べてみたのでメモっておく

普通にrequestsでHTMLを取ってきてPyQueryに渡してやると :::sh >>> from pyquery import PyQuery as pq >>> import requests >>> tgpr = "http://www.transgenic.co.jp/pressrelease/" >>> d1 = pq(requests.get(tgpr).content) >>> d1 [<{http://www.w3.org/1999/xhtml}html>] >>> d1("a") []

aタグが一件も見つからない。しかし、urlオプションに直接サイトのURLを指定するときちんとparseされている

>>> pq(url="http://www.transgenic.co.jp/pressrelease/")
[<html>]
>>> d2 = pq(url="http://www.transgenic.co.jp/pressrelease/")
>>> len(d2("a"))
71

なんだこれは?となったのでドキュメントを漁ったらxmlパーサーでパースしてみてだめだったらhtmlパーサー使うぜって書いてあったので強引にxmlパーサーでparseさせてみた。

>>> d3 = pq(url="http://www.transgenic.co.jp/pressrelease/", parser="xml")
>>> d3
[<{http://www.w3.org/1999/xhtml}html>]
>>> len(d3("a"))
0

というわけで文字列を渡すときにはなぜかhtmlのパーサーでparseされていないっぽい。これはコードを追うべきですな。

67行目のetree.XMLSyntaxErrorという例外が投げられてないのではないかということになった。

まぁ別にわざわざrequests使う必要もないのでurlオプションにURL渡せばいいんだけどね。

次回のShizuoka.pyの懇親会はやきとり王将になりました

王将使うの多分初めてかな。懇親会は予約の締め切りがあるので参加される方はお早めに

昨日は久しぶりに#A君とあって一緒に黙々コーディングをしてたんだけど、A君重量級になってた…弱虫ペダルでいうと田所感が出てた(まじで運動しないと身体にくるぞw)

また@karky7@ando_ando_andoと鈴木屋でホルモンつつきながらプログラミング言語談義でもしたいなぁと思っていたけどなかなか難しいですね。

それから#A君にはgoに奪われた静岡市圏の勢力をPyhtonで奪還するというタスクが出来たので頑張ってください。たまにはコメヤスに酒を買いにいきたいし静岡で飲みたいなぁ。

で、もくもく会では2人でTDNetのクローラー動かないなー、おかしいなーと悩んでいたんだけど、selenium2.53.2とphantomjs2.1.1の組み合わせだとswitch_to_frameメソッドがきちんと動いてないっぽいですね。chromedriverだと動いたのでダウングレードして動く組み合わせを見つけるかバグフィックスされるの待たないといけませんな。

久々にもくもくしたけど楽しかった。ここ二三年一人でコーディングすることに慣れちゃったのと、技術を追いかけなくても余裕で生きていけるようなぬるま湯に浸かりきっていたので流石にまずいなーと感じたよ。

次回のShizuoka.pyでは

  • 不適切だが違法ではない♡クローラーを作ろう
  • はじめてならアコムをやめてジョブリブでキャッシングをしよう☆

という2つの発表をします。どちらもネタに走る予定なのであまり役に立たないかもしれないけどリハビリも兼ねているので大目に見てくださいw

Shizyoka.py #5を7/9@コミュニティFで開催します

一年半ぶりにShisuoka.pyをやります。沼津のプラサヴェルデを会場にしようと思っていたんだけど、別のイベントで使ってみたら、団体登録とか予約とかちょっとめんどくさかったので前回と同じコミュニティFでやることにします。

演題絶賛募集中です。僕は来週#A君(@ando_ando_ando)とSeleniumを使ってクローラー作る予定なのでその内容を発表しようかなーと考えています。

また、懇親会の場所が決まってないのでリクエスト等あれば。ビール電車もなかなか魅力的ではあるが…

pep8はもう古いのでpycodestyleを使おう

Pythonのコーディング規約をチェックするツールにpep8があるのだけど、pycodestyleっていう名前に変わったので今後はこちらをつかえということらしい。

つらつらと眺めていたらプロジェクトの統計を取るオプションが面白そうだったのでこのブログのCMSで実行してみた。

$ pycodestyle --statistics -qq .
1       E128 continuation line under-indented for visual indent
1       E203 whitespace before ':'
5       E231 missing whitespace after ','
2       E265 block comment should start with '# '
5       E271 multiple spaces after keyword
1       E302 expected 2 blank lines, found 1
11      E402 module level import not at top of file
29      E501 line too long (115 > 79 characters)
14      E711 comparison to None should be 'if cond is None:'
1       W291 trailing whitespace

一行のコードの文字数多すぎるんだよっていうエラーが一番多いけど、SQLAlchemyのクエリって長くなりがちだから しょうがないじゃん。 \で改行すると読みにくくなるから嫌いなんだよなぁ。

Emacs+flymakeでコード書いているのでpep8をpycodestyleに変更するだけでOK

TDnet? 強いよね…

TDnet? 強いよね…右クリック禁止,jsでHTML組み立て、iframeつかいまくり、button要素でクリック阻止とすきがないね。でも俺は負けないよ。どm、DOMたちが躍動する俺のseleniumを皆さんに見せたいね

というわけで 東証のサービスをスクレイピングしたい案件が発生したのだけど、あのサイトあれだった。一応個人でRSS化しているヒトはいたのだけど、今回は色々あって自分で頑張ってみた。

  • input要素ではなくbutton要素でクリックしてsubmitする感じになっていたのでsend_keys(Keys.RETURN)
  • iframeの内部には普通にはアクセス出来ないのでswitch_to_frameで移動する必要がある

コードを一部抜粋

def access(self, code):
    url = "https://www.release.tdnet.info/index.html"
    self.driver.get(url)
    iframe = self.driver.find_element_by_tag_name("iframe")
    self.driver.switch_to_frame(iframe)
    self.driver.find_element_by_name("q").send_keys("{}0".format(code))
    self.driver.find_element_by_name("q").send_keys(Keys.RETURN)
    time.sleep(2)
    results = []
    try:                                                                                                                                                          
        result_frame = self.driver.find_element_by_name("mainlist")
        self.driver.switch_to_frame(result_frame)
        main_table = self.driver.find_element_by_id("maintable")
        for tr in main_table.find_elements_by_tag_name("tr"):
            company_name = tr.find_element_by_class_name("companyname").text
            published = tr.find_element_by_class_name("time").text
            t = tr.find_element_by_class_name("title")
            title = t.find_element_by_tag_name("a").text
            url = t.find_element_by_tag_name("a").get_attribute("href")
            results.append({"company_name": company_name,
                            "published": published,
                            "title": title,
                            "url": url})
        return results
    except WebDriverException:
        logging.debug("{} not found".format(code))
        return results

ウェブのAPIとかデータベースに接続するようなプログラムを開発するときにjoblib便利すぎる

データサイエンティスト的な仕事をしたりスクレイパー的な開発をするとキャッシュしたくなることがある。

例えばどういう時かというと

  • pandas使っていてデータベースに重い重い処理を投げて帰ってきたものをd3.jsとかseabornみたいな描画処理のところで試行錯誤したい
  • requests+pyqueryとかでスクレイピング用のコード書きたいけど、相手のサイトに何度もアクセスするのは気が引ける

で、大抵そういう時ってファイルキャッシュしておくと思うんだけど、そのコードを追加するのが結構な心理的な負担になることが多いです。

本運用時に消すの忘れてアチャーってなったりするしね。

結局キャッシュ追加削除にコードの変更っていうのは面倒くさいというかよくないので、joblibを使うとハッピー

/tmpにでもキャッシュディレクトリ設定しておいてキャッシュしたい関数にデコレーターかます。これだけ

import requests
from joblib import Memory

memory = Memory(cachedir="/tmp")

@memory.cache
def get_ct():
    r = requests.get("https://clinicaltrials.gov/search?term=IPI-145&resultsxml=true")
    return r.content

if __name__ == '__main__':
    print get_ct()

本運用ではデコレーター外すだけだから心理的負担がほぼゼロ☆

ptpythonという快適REPL

prompt_toolkit がアツいというエントリを見かけて、おーアツい!と色々見てたらptpythonなるものを見つけた。

EmacsではJedi.elつかってるし、いいかもと思ったけど、職場の開発マシンは非力でもっさりしていたが家のmacbook airでは快適だった。

1456921812

ipythonとの使い分けどうすればいいのだろうかと思った。

Shizuoka.py #4 やりました

去年の春とは異なり、今年はshizuokaではGoが盛り上がっていたみたいですが、富士川東ラインまで撤退してShizuoka.py #4を開催してきました。

静岡言語勢力図

時間が余りそうだったので、身内しかわからないようなやっつけLTを用意しましたが… jsないだろうとかいうツッコミは置いといてください。

Pythonコミュニティが東に移動したのは、とあるチームが三島に移転したり、東部の製薬企業の人たちがPythonを書き始めたりして静岡でやるよりも、三島とか富士のあたりでやるほうが都合が良くなったという理由が大きかったりします。

次回は沼津でやって懇親会をタップルーム(ベアードビール)にしようかなとか思っています。

メインの発表はIPython notebookの話をしてきましたがネタが皆無で、本当にIPythonの説明をするという… オープンなデータを使ってIPython notebookでデモをしてきました。

他の発表者のスライドはconnpassから。いろいろと勉強になりました。個人的にはpip-toolsmarshmallowがツボった感じです。

おやつはたむら屋の団子をチョイスしておきました。

1418039056

懇親会は筋肉系居酒屋で。

1418039058

プロテインを補給しつつ、楽しく会話ができましたね。

来年も開催予定ですのでまたご参加ください。