CentOS7.1にLaravelの導入
Box追加
vagrant box add centos7.1 https://github.com/CommanderK5/packer-centos-template/releases/download/0.7.1/vagrant-centos-7.1.box
初期化
vagrant init centos7.1
設定ファイルの変更
vi Vagrantfile config.vm.network "private_network", ip: "192.168.33.10" # ここの変更
ファイルの読み直し
vagrant reload
起動
vagrant up
ログイン
vagrant ssh
Remiリポジトリ追加
sudo rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
PHP7.1インスコ
sudo yum install --enablerepo=remi,remi-php71 php php-devel php-mbstring php-pdo php-gd php-xml php-mcrypt
Laravelインスコ
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" php -r "if (hash_file('SHA384', 'composer-setup.php') === '544e09ee996cdf60ece3804abc52599c22b1f40f4323403c44d44fdfdd586475ca9813a858088ffbc1f233e9b180f061') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" php composer-setup.php php -r "unlink('composer-setup.php');"
composerインスコ
curl -sS https://getcomposer.org/installer | php sudo mv composer.phar /usr/local/bin/composer
Apacheインスコ
sudo yum install httpd -y sudo systemctl start httpd sudo systemctl enable httpd
ドキュメントルート変更
vim /etc/httpd/conf/httpd.conf DocumentRoot "/var/www/html/laravel/public" sudo systemctl restart httpd
必要なパッケージのインスコ
sudo yum -y install git zip unzip
プロジェクト作成
sudo su -
composer create-project laravel/laravel /var/www/html/laravel
logout
パーミッション変更
sudo chown -R apache:apache /var/www/html/laravel sudo chmod -R 755 /var/www/html/laravel/storage
備考
スクレイピングした情報をSlackへ通知
やること
インストール
MySQL 5.7
以下の記事参照
CentOS6にMySQL5.7をyumでインストール - Qiita
文字コードの設定はクライアント側も必要なので以下の対応も行う。
vi /etc/my.cnf # 以下を各セクションへ追記 [mysqld] character-set-server=utf8 [client] default-character-set=utf8
MySQLdb(mysqlclient==1.3.12)
pip install mysqlclient
Slack(slackweb==1.0.5)
pip install slackweb
SlackのWebhook設定
以下の設定ページで新規または既存チャンネルへのインテグレーションの追加を行う。
設定完了後にWebhooへのURLが表示されるのでコピー。 (https://hooks.slack.com/services/*****こんな感じ)
サンプル
MySQLdb
# -*- coding:utf-8 -*- import MySQLdb def db_sample(): # 接続する con = MySQLdb.connect( user='YOUR_NAME', passwd='YOUR_PASSWORD', host='YOUR_HOST', db='YOUR_DB') # カーソルを取得する ## レスポンスをディクショナリで利用するための命令 cur= con.cursor(MySQLdb.cursors.DictCursor) # クエリを実行する sql = "SELECT * FROM test" cur.execute(sql) # 実行結果をすべて取得する rows = cur.fetchall() # 一行ずつ表示する for row in rows: print( "%s, %s, %s, %s" % (row['id'], row['name'], row['created_at'], row['updated_at']) ) cur.close con.close if __name__ == "__main__": db_sample()
slackweb
# -*- coding:utf-8 -*- import slackweb slack = slackweb.Slack(url="https://hooks.slack.com/services/*****") slack.notify(text="hell world")
下準備の完了
以上が必要なライブラリと設定の追加になる。 これを踏まえ、Scrapy側で操作する。
スクレイピングの実装
とあるWebページの情報を取得し、更新があればSlackへ通知対応方法になります。 迷惑を掛けるとまずいので、リンク等は架空のものとします。
プロジェクトの作成
scrapy startproject test_to_slack
spiders
実際にスクレイピングしたい処理をクラス内に記述します。 必要な処理としては以下の2点になります。
- 取得URLの設定
- データの抽出
スパイダーをテストしたいだけなら、他の実装は不要です。 このスパイダーは、該当URL先のHTMLから特定の文字列が含まれてるURLを抽出します。
# -*- coding: utf-8 -*- import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule import pytz, datetime, calendar class HogehogeSpider(CrawlSpider): name = 'hogehoge' # リンクを抽出するために考慮されるドメインを含む文字列の単一の値, またはリスト。 allowed_domains = ['www.hogehogehoge.jp'] start_urls = ['http://www.hogehogehoge.jp'] rules = ( # スパイダーがクロールを開始するURLリストの指定と、callback関数の指定。 ## 正規表現での記述が可能なため、該当の全URLを対象とする。今回は/hoge_hoge/のみ。 ## callbackで呼び出す関数を指定する。 Rule(LinkExtractor(allow='/hoge_hoge/$'), callback='parse_hoge'), ) def parse_hoge(self, response): i = {} # xpath/cssセレクタを使って該当の情報を抽出する。 ## 以下の場合は、'<html><body><div><ul><li><a>'のようなHTMLのhrefに"comic"が含まれている情報を抽出している。 i['count'] = len(response.xpath('//body/div/ul/li/a[contains(@href,"comic")]/@href')) tz = pytz.timezone("Asia/Tokyo") now = datetime.datetime.now(tz) i['updated_at'] = i['created_at'] = calendar.timegm(now.utctimetuple()) yield i
item
スクレイピングした情報を管理するためのクラス。 例えば、スパイダーでこのItemクラスに管理しているフィールドのみ使用したい場合以下のような実装になります。 フィールドで管理されていないフィールドを指定した場合はエラーを返します。
from Hogehoge.items import HogehogeItem ... i = HogehogeItem() i['count'] = len(response.xpath('//body/div/ul/li/a[contains(@href,"comic")]/@href') # 以下のフィールドはItemクラスにないのでエラー i['test'] = 9999
import scrapy class HogehogeItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() count = scrapy.Field() created_at = scrapy.Field() updated_at = scrapy.Field()
settings
各種設定を記述します。 今回は、使用するパイプラインと、MySQL、Slack関連の設定を行ないました。
ITEM_PIPELINES = { 'hogehoge.pipelines.HogehogePipeline': 1, } ... MYSQL_HOST = 'YOUR_HOST' MYSQL_DB = 'YOUR_DB' MYSQL_USER = 'YOUR_USER' MYSQL_PASSWORD = 'YOUR_PASSWORD' SLACK_WEBHOOK = 'https://hooks.slack.com/services/***'
pipline
MySQLへの登録やSlackへの通知処理を行ないます。
import MySQLdb import slackweb class HogehogePipeline(object): #def process_item(self, item, spider): # return item def __init__(self, mysql_host, mysql_db, mysql_user, mysql_passwd): self.mysql_host = mysql_host self.mysql_db = mysql_db self.mysql_user = mysql_user self.mysql_passwd = mysql_passwd @classmethod def from_crawler(cls, crawler): return cls( mysql_host = crawler.settings.get('MYSQL_HOST'), mysql_db = crawler.settings.get('MYSQL_DB'), mysql_user = crawler.settings.get('MYSQL_USER'), mysql_passwd = crawler.settings.get('MYSQL_PASSWORD') ) def open_spider(self, spider): self.conn = MySQLdb.connect( user = self.mysql_user, passwd = self.mysql_passwd, host = self.mysql_host, db = self.mysql_db, charset="utf8" ) self.cur = self.conn.cursor(MySQLdb.cursors.DictCursor) def close_spider(self, spider): self.cur.close self.conn.close def process_item(self, item, spider): select_sql = "SELECT * FROM `test`;" self.cur.execute(select_sql) webhook_url = spider.settings['SLACK_WEBHOOK'] slack = slackweb.Slack(url=webhook_url) # 登録済みなら登録件数と今回取得した件数の比較を行う if self.cur.rowcount: # 登録件数が異なれば更新 if self.cur.fetchone()['count'] != item['count']: update_sql = "UPDATE `test` SET count = %s, updated_at = %s;" self.cur.execute(update_sql, (item['count'], item['updated_at'])) self.conn.commit() to_comic_url = "http://www.hogehoge.jp/comic%s.png" % item['count'] slack.notify(text=to_comic_url) else: # 同じならupdated_atのみ更新 update_sql = "UPDATE `test` SET updated_at = %s WHERE count = %s;" self.cur.execute(update_sql, (item['updated_at'], item['count'])) self.conn.commit() else: # 未登録なら登録する insert_sql = "INSERT IGNORE INTO `test` (`count`, `created_at`, `updated_at`) VALUES (%s, %s, %s);" self.cur.execute(insert_sql, (item['count'], item['created_at'], item['updated_at'])) self.conn.commit() to_comic_url = "http://www.hogehoge.jp/comic01.png" slack.notify(text=to_comic_url)
scrapyの導入と標準入出力で躓いたこと
結論
環境
CentOS 6 Python 3.6.4 Scrapy 1.5.0
経緯
Scrapyを仮想環境で試そうとしたところ、環境を整え直す羽目になったのでφ(..)メモメモと。
発生した問題
ModuleNotFoundError: No module named ‘_sqlite3'
scrapy shellを実行したところ、以下のエラーが発生。 SQLite3の開発用ツールとPython3の再インスコが必要。
(略) File "/home/vagrant/.pyenv/versions/3.6.4/lib/python3.6/sqlite3/dbapi2.py", line 27, in <module> from _sqlite3 import * ModuleNotFoundError: No module named '_sqlite3'
標準入出力の文字化け
cssセレクタを使い日本語の抽出を行ったところ、文字化けが発生。 こちらは環境変数でエンコーディングの指定が必要。
対応方法
ModuleNotFoundError: No module named ‘_sqlite3'
SQLite開発用ツールを入れた後改めて、Pythonのコンパイル、インストール。
sudo yum install sqlite-devel -y sudo rm -rf ~/.pyenv/shims/ /bin/bash -lc "pyenv install 3.6.4 && pyenv rehash && pyenv global 3.6.4"
標準入出力の文字化け
標準入力/標準出力/標準エラー出力用環境変数の上書き指定。 bashrcに追記。
export PYTHONIOENCODING=utf-8
おまけ
ファイルやjsonを使用した際の出力でも文字化けが起こるので以下の設定をsettings.pyへ追記する。
FEED_EXPORT_ENCODING = 'utf-8'
参考
pythonにsqlite3が無いと怒られる - よしだです