ボールを蹴りたいシステムエンジニア

ボール蹴りが大好きなシステムエンジニア、ボールを蹴る時間確保の為に時間がある時には勉強する。

LinuxのiptablesでTomcatのポートを開放する

VMware上のTomcatにホストマシン(Winsows10)からブラウザでアクセスする為に、Tomcatのポートを開放する。

環境

CentOS 6.6

手順

iptables編集

[root@localhost src]# vi /etc/sysconfig/iptables

追記

-A INPUT -m state --state NEW -m tcp -p tcp --dport 8080 -j ACCEPT  

注意点として、REJECTの行の前に追記する事。
知らずに最終行(COMMIT)の前に追記した時は許可されなかった。

iptables再起動

[root@localhost src]# /etc/rc.d/init.d/iptables restart

アクセスできた
http://192.168.10.111:8080/

scrapyで独自設定値を設定ファイルに追加して利用する

scrapyで独自設定値を設定ファイルに追加して利用する方法。

setting.pyに追記

TESTKEY = HOGEHOGE

こんな感じで利用

from scrapy.conf import settings

 ~~~

value = settings.get("TESTKEY")

int型、boolean型、float型の指定も出来るみたい。

settings.getint("TESTKEY")

settings.getfloat("TESTKEY")

settings.getbool("TESTKEY")

第2引数でデフォルト値も設定できるみたい

value = settings.get("TESTKEY", "DEFAULT")

dic型も利用できる

COOKIES = {
    "key1": "value21",
    "key2": "value2"
}
value = settings.get("COOKIES")

scrapyでcookieを設定してクロールする

spidersパッケージ内のクロール処理メインのモジュールで以下のようにmake_requests_from_urlを定義してその中でcookieセット処理を実装する事でログインが必要なサイトでもクロールできた。

class ExampleSpider(CrawlSpider):

 ~~~

    def make_requests_from_url(self, url):
        request = super(ExampleSpider, self).make_requests_from_url(url)
        request.cookies['test_key'] = 'value1'
        request.cookies['test_key2'] = 'value2'

        return request
    

注意点としてCrawlSpiderの継承クラスである事。
scrapy.Spiderの継承クラスの場合は異なるっぽい事をどっかで見かけた。(未検証)

Python製のクローラー「scrapy」の利用方法や初期設定など纏め

Python製のクローラーフレームワークscrapyを使用してクローラーを構築する。

環境

Windows10 64bit
Python for Windows(3.5.1)
Eclipse4.6
PyDev(Eclipseプラグイン)
Cygwin

やりたい事

どこかのサイトのクローラーを1時間で実装
今回は参考サイトと同じグノシーを対象とする。

手順

クローラプロジェクトの雛形作成

$ scrapy startproject gunosy_crawl

スパイダーの作成

scrapy genspiderの第1引数はPythonファイル名、第2引数はクロール対象サイトのドメイン

$ cd gunosy_crawl/
$ scrapy genspider gunosy gunosy.com

起点URLを定義

/gunosy_crawl/gunosy_crawl/spiders/gunosy.py

  • start_urls

クロール起点URLを定義、タプルで複数指定も可能。

  • allowed_domains

クロール許可ドメイン、配列で複数指定可能

class GunosySpider(scrapy.Spider):
    name = "gunosy"
    allowed_domains = ["gunosy.com"]
    start_urls = (
    'https://gunosy.com/'
    )

アイテムクラスを実装

クロール結果を保存するアイテムクラスを実装します。

gunosy_crawl/gunosy_crawl/items.py

class GunosyCrawlItem(scrapy.Item):
    title = scrapy.Field()
    url = scrapy.Field()
    text = scrapy.Field()

今回はタイトルとURLと本文を設定する。

パース処理実装

今回はグノシーのニュース記事ページから記事本文を取得対象とする。
例としてこのようなページ
https://gunosy.com/articles/Rol5u

このページのソースをブラウザで確認して記事本文エリアのタグを取得する。
見た感じこれ

<div class="article gtm-click" data-gtm="article_article">
Scrapy Shellによるデバッグ

scrapyのデバッグ機能を使って当該ページのパース処理を確認

$ scrapy shell https://gunosy.com/articles/Rol5u  

実行するとiPythonが起動する。

タグに対応したパース処理実行

response.xpath('//div[@class="article gtm-click"]/*/text()').extract()

テキスト取得された。
当方はcygwinで実行している為、日本語が表示されていないが恐らく問題無し。
配列データとなってるのはdivタグ内のpタグ分が存在している為と思われる。

In [1]: response.xpath('//div[@class="article gtm-click"]/*/text()').extract()
Out[1]: 

['929',
 '',
 '4',
 '4',
 '4,000iOSAndroidMacWindows342',
 '',
 '3',
 '13,5003',
 '22006Discover WeeklyRelease Reader',
 '3Spotify Free',
 '3',
 '']
パース処理の実装

/gunosy_crawl/gunosy_crawl/spiders/gunosy.py

    def parse(self, response):
        
        article_text = response.xpath('//div[@class="article gtm-click"]/*/text()').extract()
        #取得対象のページのみヒットするはず
        if article_text is not None :
            article = GunosyCrawlItem()
            article['title'] = response.selector.xpath('//title/text()')
            article['url'] = response.urljoin
            article['text'] = article_text
            yield article
        else:
            print("not get text data : {0}".format(response.urljoin))
        
        linklist = response.xpath('//a/@href').extract()
        for link in linklist:
            yield scrapy.Request(link, callback=self.parse)
Parse コマンドによるデバッグ
scrapy parse --spider=gunosy https://gunosy.com/articles/Rol5u

以下のように、Scraped ItemsとRequestsの結果を見て、スクレイピング結果とクロールURLの取得が出来ている事を確認します。

# Scraped Items  ------------------------------------------------------------
[{'text': '929 '
          ' '
          '4 '
          '4 '
          '4,000iOSAndroidMacWindows342 '
          ' 3 '
          '13,5003 '
          '22006Discover '
          'WeeklyRelease '
          'Reader '
          '3Spotify '
          'Free '
          '3 '
          '',
  'title': '... - ',
  'url': 'https://gunosy.com/articles/Rol5u'}]
# Requests  -----------------------------------------------------------------
[<GET https://gunosy.com/>,
 <GET https://gunosy.com/users/sign_in>,
 <GET https://gunosy.com/sitemap>,

以下のようなエラーが出た場合、yield scrapy.Requestで返してるURLが不正という事らしいです。

ValueError: Missing scheme in request url: //gunosy.co.jp/

対策として以下のようにurljoinメソッドを使って整形しましょう

            yield scrapy.Request(response.urljoin(link), callback=self.parse)

保存先指定

gunosy_crawl/gunosy_crawl/settings.py

# シードURLのファイル形式
FEED_FORMAT = 'csv'

# シードURLの保存先
FEED_URI = 'file:///tmp/export.csv'

2016/9/5時点では、jsonとjsonlinesは日本語ユニコードデコードに未対応なので使いたい場合はカスタマイズが必要らしい。
Python: Scrapy と BeautifulSoup4 を使った快適 Web スクレイピング | CUBE SUGAR STORAGE

その他設定

gunosy_crawl/gunosy_crawl/settings.py

#ユーザーエージェント
USER_AGENT = 'gunosy_crawl (+http://www.yourdomain.com)'

#True = Webサイトのrobots.txtに従う
ROBOTSTXT_OBEY = True

#Webページのダウンロード間隔
DOWNLOAD_DELAY = 2

#ログ出力先
LOG_FILE = '/tmp/scrapy.log'

色々やりたい時

スクレイピングURLルールを指定したい場合

まず、スーパークラスを変更

class GunosySpider(scrapy.Spider):

↓変更↓

class GunosySpider(CrawlSpider):

スクレイピング対象URLとか指定する

    # スクレイピングを開始するURL、複数指定可
    start_urls = ['https://gunosy.com/']
    
    # スクレイピング対象のパスパターン、ドメイン以下のURLに関して正規表現で指定します
    allow_list = ['/articles/']
    
    # スクレイピング対象外パスパターン、ドメイン以下のURLに関して正規表現で指定します
    deny_list = ['/categories/', '/ranking/']

    rules = (
            # スクレイピングするURLのルールを指定
            Rule(LinkExtractor( allow=allow_list, deny=deny_list), callback='parse_item'),
            # spiderがたどるURLを指定
            Rule(LinkExtractor(), follow=True),
        )

上記callbackで指定した関数を実装。
元々あったparse関数は削除。
※parse関数がある場合はparse関数が優先して実行されるので注意

    def parse_item(self, response):
        
        article_text = response.xpath('//div[@class="article gtm-click"]/*/text()').extract()
        article = GunosyCrawlItem()
        article['url'] = response.urljoin('')
        article['title'] = response.selector.xpath('//title/text()').extract()[0].replace('\n','')
        article['text'] = ' '.join(article_text).replace('\n','')
        print("article_text2 : {0}".format(article))
        #クロール結果を出力
        yield article 
            
        #ページ内の全てのaタグリンクをクロール
        linklist = response.xpath('//a/@href').extract()
        for link in linklist:
            # 次のクロール対象を渡す
            yield scrapy.Request(response.urljoin(link), callback=self.parse)
日本語Unicodeデコードした文字列でjson形式で出力

scrapyでは出力フォーマットの形式をexporterで拡張できるらしい。
独自モジュールを実装して日本語エスケープ文字列を出力前にデコードする。
gunosy_crawl/gunosy_crawl/exporters.py
を新規作成

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from scrapy.contrib.exporter import JsonLinesItemExporter


class NonEscapeJsonLinesItemExporter(JsonLinesItemExporter):

    def __init__(self, filepath, **kwargs):
        super(NonEscapeJsonLinesItemExporter, self).__init__(
            filepath,
            ensure_ascii=False
        )

設定モジュールも編集
gunosy_crawl/gunosy_crawl/settings.py

FEED_FORMAT = 'jsonlines'

FEED_URI = 'file:///tmp/export.json'

FEED_EXPORTERS = {          
    'jsonlines': 'gunosy_crawl.exporters.NonEscapeJsonLinesItemExporter',  
}

jsonlinesというのはJSONを行形式にしたものらしく、その形式を指定。
FEED_EXPORTERSという変数で拡張したexporterを指定する。
ここでjsonlinesを指定して、jsonlinesの時はこのexporterを使用するという意味らしい。

ログ出力

バージョン1.0より前ではscrapy.log.msgメソッドを使用していたようだけど、現在はビルトインのloggingを利用推奨みたい。

import logging
logging.warning("This is a warning")

実行

$ scrapy crawl gunosy

取得結果を確認

less /cygdrive/d/tmp/export.csv 

おわりに

  • scrapy1.0でpython3に対応してからは日本語の解説ページも増えてきてるみたい。
  • 公式のドキュメントを読み漁ればもっと色々できそう。
  • 学習コストもそこまで高くない気がするので、汎用的な処理を実装しておきサイト個別の処理を設定ファイルとかに纏めておけば新たなサイトのクローラーを構築するのも直ぐにできそう。
  • DBとの連携も出来るらしいので今後調査
  • プロセスが落ちた後、新たにプロセスを立ち上げると一度クロール済みのページも再度取得済みなるらしいので対策が無いか調査する
  • Scrapy Cloudというサービスがあるらしいので要調査

Windowsで最強のPython開発環境を構築するまで【都度更新】

WindowsでのPython開発環境。
試行錯誤の上ようやく落ち着いてきたので纏め。
随時更新する。

全然最強じゃ無いじゃん、とかのツッコミは無しで。

環境

Windows10 64bit
Python for Windows(3.5.1)
Eclipse4.6
PyDev(Eclipseプラグイン)
Cygwin
VMware

希望する開発環境

Python for Windows 64bit

Windowsなのでもちろんこれ。
cygwinでの作業も考えてcygwin内のpythonを使用していた時期もあったがライブラリ周りで躓く事が多かった為、Windows版の利用に落ち着いた。

PyDev(Eclipseプラグイン

IDEEclipseプラグインPyDevを利用。
コード補完とブレークポイントによるデバッグも可能。

色々試したがイマイチ使いずらかったIDE

SublimeText2
  • プラグインをインストールして色々設定は出来る。
  • コード補完もコードチェックも可能
  • SublimeText上からpython実行も可能

使いづらいという訳では無いけど何かもの足りなかった。
IDEに乗り換えたい気持ちが日に日に募ってきたので使うのをやめた。
導入方法例↓

SublimeText2でのpython3開発環境構築 - ボールを蹴りたいシステムエンジニア

PyCharm
  • コード補完の挙動が怪しかった(保管されないコードがある等)
  • 日本語に対応しておらずたまにメニュー操作でハマる

ネットを見る限り使用者は多そう、知り合いのエンジニアも使っていた。
恐らく自分が知識低いだけと思うけど、使いにくさを感じた。

Visual Studio Express 2015 Python Tools
  • デバッグもコード補完も出来てまぁまぁ快適
  • 評価版だったようで、30日過ぎたら使用できなくなった
  • VisualStudiという事でソリューションディレクトリや専用ファイルが作成されたりするのが何か嫌
  • コード補完がたまに怪しい

個人的にはまあまあ使い勝手は良かったので今後ずっとこれを使ってく予定だったけど試用期間過ぎたら使えなくなったので使うのをやめた。
恐らく無償版はあると思うけど調べてない。

コード補完の設定でハマった時のメモ↓

Visual Studio Express 2015 Python Toolsでコード補完が効かない時の対処方法 - ボールを蹴りたいシステムエンジニア

vim

機械学習ライブラリの関係でWindowsでの動作確認が色々と出来なかった事を理由にWindowsでの開発をやめて、仮想環境VMwareLinuxvimで開発。
jedi-vimが重すぎた事が原因で使うのをやめた↓

jedi-vimをインストールしてvimでpythonコードの補完 - ボールを蹴りたいシステムエンジニア

Cygwin

ローカル環境でも出来る限りLinuxに近い形で動作確認を行う為に利用。
PyDev上でのpythonの実行動作確認は行えるが、結局本番環境はLinuxなので。
人によっては不要かもしれないが、自分はコマンド操作が楽で好きなので利用。

注意点としてcywinでのpython実行はcygwinpythonでは無くwindowspythonが実行されるようPATHの設定やalias設定を行う事。
自分の環境では以下のようにcygwinの.bashrcに記述して利用している。

alias python-windows='/cygdrive/c/Python3.5/python.exe'
alias pip-windows='/cygdrive/c/Python3.5/Scripts/pip3.5.exe'

サクラエディタとかcygwinから使いたかったら適宜設定する

function sakura() {
    cygstart /cygdrive/c/Program\ Files\ \(x86\)/sakura/sakura.exe `cygpath -aw $*` &
}

VMware

開発環境というよりは学習や動作検証の為に利用。
機械学習ライブラリ周りの学習の為にはやはりLinux環境での確認が色々と捗る。
PyDevでの実装コードとVMwareのソース連携はcygwinからscp経由で連携する。

Windowsでのpython開発環境で気を付ける事

ライブラリのインストール

後述する機械学習ライブラリも該当するが、pipを利用してのライブラリインストールは(gccビルド等)本来Linxu環境での動作が想定されている為、windows環境でインストールをしても正常に動作しない事がある。

非公式ながら有志の方が作成したWindows版のライブラリをWheelファイルからインストールする。
当該ライブラリ名で検索して環境・バージョンに適したファイルをダウンロードする。

Python Extension Packages for Windows - Christoph Gohlke


Wheelファイルからのインストールでも少しハマったのでその時の記事↓

【解決】続・Windows10のpython3.5でlxmlをインストールしようとしたらハマったのでメモ - ボールを蹴りたいシステムエンジニア


Mecab

単純にMecabを使うだけなら公式のインストーラーからの導入で問題無く使えるんだけど、pythonで利用する為のmecab-pythonを導入がかなり面倒くさい。
MeCabWindowsにおいてはVSを使ってコンパイルするようにソースコードが作られている為VSのインストールが必要。
更に公式のWindowsMecabが32ビット版しか無い為色々とソースの修正が必要となる事等があり、かなりハマった。
何とか問題無く利用できるようになったけど何度挫折しようとした事か。
その時の記事↓

WindowsのPythonでMecabを実行する(+Cygwinでも実行する) - ボールを蹴りたいシステムエンジニア

かなりハマった。

機械学習ライブラリ

scipyのインストールで結構ハマった。
機械学習ライブラリにはnumpyやscikit-learn等もあるが、依存ライブラリとしてscipyも必要となる。
Linuxでは数値演算ライブラリとしてlapack/blasをインストールしているけど、windowsではそれらに対応してない?為、mklをインストールして代用する。

その時記事↓

Windows環境でpythonの機械学習ライブラリscipyのインストールでハマった時の対処 - ボールを蹴りたいシステムエンジニア

おわりに

Windowsでの開発環境構築では色々な場面でハマってその度にMacを購入しようかと何度考えた事か。
ネット上のpython参考サイトもMac利用者が多いし。
とりあえず現在の開発環境で不満無くサクサクpython開発が行えるし、個人的にはベストな開発環境だと思う。
良いツールがあれば都度開発環境をアップデートしていきたい。