pythonを使ってcsvのファイルを生成する方法
pythonを使ったcsvのファイルを生成する方法を紹介します。
この記事では、csvの読み込みは扱いません。csvファイルを作成する方法を紹介しています。
csvを扱うための便利なライブラリをインストールします。
pip install pandas
csvを読み書きするためにpandasをインストールします。
pandasをインストールするとnumpyもインストールされます。
csvファイルを生成する関数を作成します。
output_csv_root = 'csv/files' def gen_csv(dataset, file_name): df = pd.DataFrame(dataset) file_path = os.path.join(output_csv_root, file_name + '.csv') df.to_csv(file_path)
gen_csv
を実行すると、{output_csv_root}/{filename}.csv
が作成されます。
datasetに指定する値は、二次元の配列を指定します。
[[1,2,3], [4,5,6]]
これがこうなります。
#, 1, 2, 3 1, 1, 2, 3 2, 4, 5, 6
一行目の#, 1, 2, 3
は見出しです。
この見出しは、自分で指定することもできます。
指定する場合は、dataFrameを作成する際にcolumnsを指定します。
df = pd.DataFrame([rows], columns=['A', 'B', 'C'])
また、行番号が必要ない場合は、このようにindex
をFalseにします。
df.to_csv(file_path, index=False)
mecabを使って解析する方法
mecabのインストール方法等はいろいろなサイトに書かれているので、pythonのプログラミング部分を紹介します。
意外と実装について書かれてる記事が少なくてびっくりしました。なので、help()を使って使い方を試しました。
以下の環境で作業しています。
mac osX brewでmecabとmecab-ipadicをインストール mecab-python3 mecab-ipadic-neologd
mecabインスタンスにはparseToNode(self, *args)
やparseToString(self, *args)
があります。
このうち、parseToString(self, *args)
は文字列にパースされるので少し使いづらいです。
なので、parseToNode(self, *args)
を使用します。
mecab = MeCab.Tagger('-d /usr/local/lib/mecab/dic/mecab-ipadic-neologd') novel_data = novel_list.get(0) node = mecab.parseToNode('Google Analytics(グーグルアナリティクス)は、Googleが無料で提供するWebページのアクセス解析サービス。') while node: surface = node.surface feature = node.feature print(surface, feature) node = node.next
このようなデータを取得できます。
BOS/EOS,*,*,*,*,*,*,*,* Google Analytics 名詞,固有名詞,一般,*,*,*,Google Analytics,グーグルアナリティクス,グーグルアナリティクス ( 記号,括弧開,*,*,*,*,(,(,( グーグルアナリティクス 名詞,固有名詞,一般,*,*,*,Google Analytics,グーグルアナリティクス,グーグルアナリティクス ) 記号,括弧閉,*,*,*,*,),),) は 助詞,係助詞,*,*,*,*,は,ハ,ワ 、 記号,読点,*,*,*,*,、,、,、 Google 名詞,固有名詞,一般,*,*,*,Google,グーグル,グーグル が 助詞,格助詞,一般,*,*,*,が,ガ,ガ 無料 名詞,一般,*,*,*,*,無料,ムリョウ,ムリョー で 助詞,格助詞,一般,*,*,*,で,デ,デ 提供 名詞,サ変接続,*,*,*,*,提供,テイキョウ,テイキョー する 動詞,自立,*,*,サ変・スル,基本形,する,スル,スル Webページ 名詞,固有名詞,一般,*,*,*,Webページ,ウェブページ,ウェブページ の 助詞,連体化,*,*,*,*,の,ノ,ノ アクセス解析 名詞,固有名詞,一般,*,*,*,アクセス解析,アクセスカイセキ,アクセスカイセキ サービス 名詞,サ変接続,*,*,*,*,サービス,サービス,サービス 。 記号,句点,*,*,*,*,。,。,。 BOS/EOS,*,*,*,*,*,*,*,*
surfaceは単語を取得でき、featureではその解析結果が取得できます。
結果を見るとわかるかと思いますが、BOS/EOS,*,*,*,*,*,*,*,*
というのが出てきています。
これを除きつつ、データを使いやすいように取得します。
def main(): mecab = MeCab.Tagger('-d /usr/local/lib/mecab/dic/mecab-ipadic-neologd') node = mecab.parseToNode('Google Analytics(グーグルアナリティクス)は、Googleが無料で提供するWebページのアクセス解析サービス。') node_list = [] while node: surface = node.surface feature = node.feature c = feature.split(',')[0] if not c == 'BOS/EOS': node_list.append({ 'surface': surface, 'feature': feature, }) node = node.next for n in node_list: print(n) if __name__ == '__main__': main()
実行結果がこちらです。
{'surface': '', 'feature': '名詞,固有名詞,一般,*,*,*,Google Analytics,グーグルアナリティクス,グーグルアナリティクス'} {'surface': '\t', 'feature': '記号,括弧開,*,*,*,*,(,(,('} {'surface': 'グーグルアナリティクス', 'feature': '名詞,固有名詞,一般,*,*,*,Google Analytics,グーグルアナリティクス,グーグルアナリティクス'} {'surface': ')', 'feature': '記号,括弧閉,*,*,*,*,),),)'} {'surface': 'は', 'feature': '助詞,係助詞,*,*,*,*,は,ハ,ワ'} {'surface': '、', 'feature': '記号,読点,*,*,*,*,、,、,、'} {'surface': 'Google', 'feature': '名詞,固有名詞,一般,*,*,*,Google,グーグル,グーグル'} {'surface': 'が', 'feature': '助詞,格助詞,一般,*,*,*,が,ガ,ガ'} {'surface': '無料', 'feature': '名詞,一般,*,*,*,*,無料,ムリョウ,ムリョー'} {'surface': 'で', 'feature': '助詞,格助詞,一般,*,*,*,で,デ,デ'} {'surface': '提供', 'feature': '名詞,サ変接続,*,*,*,*,提供,テイキョウ,テイキョー'} {'surface': 'する', 'feature': '動詞,自立,*,*,サ変・スル,基本形,する,スル,スル'} {'surface': 'Webページ', 'feature': '名詞,固有名詞,一般,*,*,*,Webページ,ウェブページ,ウェブページ'} {'surface': 'の', 'feature': '助詞,連体化,*,*,*,*,の,ノ,ノ'} {'surface': 'アクセス解析', 'feature': '名詞,固有名詞,一般,*,*,*,アクセス解析,アクセスカイセキ,アクセスカイセキ'} {'surface': 'サービス', 'feature': '名詞,サ変接続,*,*,*,*,サービス,サービス,サービス'} {'surface': '。', 'feature': '記号,句点,*,*,*,*,。,。,。'}
いい感じです。
使うときはforでループして、dictからデータを取得すると良いでしょう。
もしくはこのようにデータを追加して、tupleで使用してもよいと思います。
node_list.append( (surface, feature,) )
使用するときはこんな感じです。
for s, f in node_list: print(s, f)
DjangoでCSRFを含んだリクエストをtagを使って実行する方法
CSRFトークンを含んだAPIの実行方法で、ドキュメントに載っているような方法ではなく、tagを使った方法を紹介します。
tagを使った方法というのは、{% cookie 'csrftoken' %}
でhtml内にtokenを入れて、その値を使う方法です。
修正が必要な箇所は以下の3箇所です。
- Django
- html(template)
- javascript
Djangoの対応
まずは、テンプレートから使用できるようにtagを作成します。
作成する場所は、プロジェクトディレクトリがある場所にtemplate_tags/tags.py
というファイルを作成しました。
@register.simple_tag(takes_context=True) def cookie(context, cookie_name): # could feed in additional argument to use as default value request = context['request'] result = request.COOKIES.get(cookie_name, '') # I use blank as default value return result
次にsettings.pyを編集します。
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], 'libraries': { # ←これを追加 'tags': 'template_tags.tags', } }, }, ]
tagのファイルを読み込むようにしています。
これでDjango側の対応は終わりです。
html(template)の対応
Djangoで使用するtemplate html内に以下のように書きます。
<meta name="csrf" content="{% cookie 'csrftoken' %}">
最終的にhtmlのmetaタグにcsrfを追加して、jsで読み込んで使用します。
<meta name="csrf" content="W3ni7NdZeaPWYgfoRShxFVNLHCimZWxpZsL8QMJ4VtlBguv9lo7NoYGO7Gl0eD0D">
javascriptの対応
javascriptの対応も簡単です。
function call(callback) { $.ajax({ url:'/api/hoge', type:'POST', beforeSend: function(request) { let csrftoken = $("meta[name='csrf']").attr('content') request.setRequestHeader("X-CSRFToken", csrftoken); }, data:{ 'limit': 100, } }) .done(function(data) { console.log(data); callback(data); }) .fail(function(data) { console.log(data); }) }
この部分を追加すると対応が可能です。
beforeSend: function(request) { let csrftoken = $("meta[name='csrf']").attr('content') request.setRequestHeader("X-CSRFToken", csrftoken); },
また、状況に合わせて以下のコードを入れると良いとおもいます。
csrfが必要なリクエストと、不要なリクエストを分けて、必要ならcsrftokenありでリクエストするものです。
function csrfSafeMethod(method) { // these HTTP methods do not require CSRF protection return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); } function ajaxInit() { $.ajaxSetup({ beforeSend: function(xhr, settings) { let csrftoken = getCookie('csrftoken'); if (!csrfSafeMethod(settings.type) && !this.crossDomain) { xhr.setRequestHeader("X-CSRFToken", csrftoken); } } }); }
tagを使う方法はstackoverflowで見つけました。
python - Django read cookie in template tag - Stack Overflow
失敗パターン
csrf_tokenで対応しようとして失敗
Djangoではformでリクエストを実行する際に{% csrf_token %}
というものを使えます。
これを使えばtoken値を上記みたいなコードを使わずに簡単に実装できるのではと考えました。
metaタグにcsrf_tokenを入れれると思ったのですが…
<meta name="csrf" content="{% csrf_token %}">
{% csrf_token %}
が展開されると<input>
タグが作成されるのでダメでした。
画面上に/>
という文字列が表示されていたり、domがめちゃくちゃになったり(body直下にheadがあった)しました。
あえなく断念しました。
■
DjangoでSerializerを使ったバリデーション方法について
DjangoではRestApiを作成するための便利なライブラリとしてDjango REST framework
というものがあります。
このライブラリを使うと、簡単にapiを作成することができます。
この記事ではDjango REST framework
で用意されているserializer
を使ったバリデーション方法を紹介します。
また、Django REST framework
でviewを表示する方法についても紹介します。
views.py
views.py
ではviewsetを使って実装しています。
action
を使って、get
やpost
といったメソッドを作らないようにしました。
action
を使った場合はurls
を少し工夫する必要がありますが、それについてはurls.pyを見てください。
from django.http import JsonResponse from django.shortcuts import render # Create your views here. from rest_framework import viewsets from rest_framework.decorators import action from apps.xxxxxxx.serializer import SampleSerializer class SampleViewSet(viewsets.ModelViewSet): serializer_class = SampleSerializer @action(detail=False, name='sample_post_api') def sample_post(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) if not serializer.is_valid(): return JsonResponse({"status": 400}, status=404) return JsonResponse(serializer.data, status=200) @action(detail=False, name='sample_get_open_view') def sample_get(self, request, *args, **kwargs): return render(request, 'home.html', status=200)
saple_get
ではrenderというショートカットを使用してhome.html
を表示しています。
restframework
のサンプルでは以下のように実装されているので、以下の方法でも良いです。
@action(detail=True, renderer_classes=[renderers.StaticHTMLRenderer]) def highlight(self, request, *args, **kwargs): snippet = self.get_object() return Response(snippet.highlighted)
serializer.py
countというフィールドを持っています。
countは-1か1の値を受け付けます。
IntegerField
ではmin_value
とmax_value
を使えますが、0は許可してほしくありません。
そういった場合はvalidation_xxxx
というようにバリデーションを追加できます。
from rest_framework import serializers class SampleSerializer(serializers.Serializer): count = serializers.IntegerField(min_value=-1, max_value=1) def validate_count(self, count): if count == 0: raise serializers.ValidationError("count is not allowed zero.") return count
新しいシリアライザクラスを作成したほうが良いのかもしれません。
urls.py
views.py
で作成したapiを登録します。
restframework
ではrouterというものを使うのが一般的だと思いますが、この記事ではurlpatternsをそのまま使ってます。
routerについてはこちらを参照してください。
https://www.django-rest-framework.org/api-guide/routers/
また、検索するとよく見つかるurl()
というのの代わりにpath
とre_path
を使用しています。
理由はurlが廃止される可能性があるからです。
from django.contrib import admin from django.urls import path, re_path from apps.xxxxxxxx.views import SampleViewSet # admin urlpatterns = [ path('admin/', admin.site.urls), ] # sample api and view urlpatterns += { re_path(r'sample/?$', SampleViewSet.as_view(actions={ 'get': 'sample_get', })), path('api/sample/post', SampleViewSet.as_view(actions={ 'post': 'sample_post' })), }
.as_view
の’action
を使って登録します。
見るだけでわかると思います。
getでリクエストが来たら、sample_get
メソッドへ。
postでリクエストが来たら、sample_post
メソッドへ。
ちなみに、以下のように実装しないのには理由があります。
urlpatterns += { re_path(r'sample/?$', SampleViewSet.as_view(actions={ 'get': 'sample_get', 'post': 'sample_post' })), }
このようにすると、/sample
にリクエストした際に、restframework
が用意している管理画面を表示できません。
postをテストしたいのに、getが最初に表示されるので、管理画面が表示されないのです。
ハロウィンだしネタアプリを作ってみた話
タイトルの通り、ネタアプリを作ってみました。
作ったアプリケーションはこちらです。
「進捗どうですか?」というアプリケーションで、作成時間は1~2時間ぐらいです。
使用したフレームワークや環境を箇条書きにするとこんな感じです。
- Django2.1.2
- heroku free
- python3.6.4
- heroku postgresql
- domain 1円
これまで書いてきた記事を参照しつつ短時間で作ることができました。
ブログを書き続けてよかったなと感じた瞬間です。
あと、webサービスを作りたい人や、プログラミングの勉強をしている人は今回私が作ったような小さなアプリケーションから始めると良いと思います。
小さなアプリケーションでも、大きなアプリケーションでもやることは変わらないからです。
設計して、開発して、テストして、デプロイして、、あれこれと、リリースまでにやることが体験できます。
最初は時間がかかりますが、何度かやっているとやり方が分かるので、3回ぐらいやるとパパっと作れるようになります。
そして、慣れたら大きなサービスを作ってみましょう。
きっとうまくいきます
はみ出す文字列を三点リーダーにする方法
領域をはみ出したときに三点リーダー(…)にする方法を紹介します。
既出かと思いますが、詳しい内容とかあまり見つからない(こうやればできるというのは見つかる)ので、私なりに調べた内容を載せます。
領域をはみ出したときに三点リーダー(…)にする場合は以下のように設定します。
span.ellipsis { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
spanタグのclassにellipsis
を設定した場合、はみ出した部分を三点リーダーにします。
spanタグが対象なのは特に意味はないです。強いて言うならellipsisの使い方をわかりやすくするためです。
※ このスタイルでは複数行の対応はできません。
overflow:hiddenについて
領域をはみ出した際に、はみ出したものを表示しないようにします。
scrollとか、autoにするとスクロールバーが出るやつです。
余談ですが、最近はscrollとかautoは使わなくてperfect-scrollbar.js
というのを使ってます。
white-space:nowrapについて
空白文字の扱いを決めています。
nowrapを指定すると、一行で表示されます。
preでもできると思いますが、改行やタブが残るのでうまくいかない場合があると思います。
こちらのページがわかりやすいと思います。
https://developer.mozilla.org/ja/docs/Web/CSS/white-space
text-overflow: ellipsisについて
はみ出した部分を三点リーダーにします。
おまけ
youtubeのサイドバーを見てみると、同じようなことをやってるのがわかります。
さらに、右側に24pxの余白をいれて、見やすくするというテクニックを発見できます。
24pxというのは1.5rem(1rem=16pxのとき)なので何か意味があるのだと思います。
なんの意味があるのかはわからないです。
margin-right: 24px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: 1.4rem; font-weight: 400; line-height: 2.1rem; -ms-flex: 1 1 0.000000001px; -webkit-flex: 1; flex: 1; -webkit-flex-basis: 0.000000001px; flex-basis: 0.000000001px;
lxmlを使ったxmlのパース方法
lxmlというライブラリを使ってxmlをパースしたときのメモを記事にしています。
lxmlはBeautifulSoupというスクレイピングのライブラリでも使われたりしなかったりします。
xmlファイルを取得する
pythonでファイルを読み込む方法はいろいろありますが、再帰的にファイルを取得しつつ、指定したディレクトリ以下のxmlファイルをすべて取得します。
from lxml import etree from pathlib import Path def get_xml_files(target): files = list(Path(target).glob('**/*.xml')) return files
get_xml_files
を実行すると、指定したディレクトリ以下のxmlファイルのパスがすべて取得できます。
このパスをlxmlで読み込んで、利用します。
lxmlでxmlをパースする
get_xml_files
を使って、ファイルまでのパスを取得後にパース処理をします。
xml_files = get_xml_files(xml_target) for xml_file in xml_files: xml_file_name = str(xml_file) tree = etree.parse(xml_file_name)
xml_file
はPosixPoth
クラスのインスタンスなので、そのままetree.parse
に渡せません。
一度strで文字列にしてからparseします。
コメントの削除方法
xmlのコメントが残っていると、forでループする際にcommentが引っかかります。
これを解消するために、removeしようと考えたのですが、remove系の関数がありませんでした。
理想ではtree.rm_comments()
とかetree.rm_comments()
があったらよかったです。
というわけで、対処方法が以下のプログラムです。
def remove_comments(tree): comments = tree.xpath('//comment()') for comment in comments: parent = comment.getparent() parent.remove(comment)
tagを判定する
以下のプログラムは、mybatisというjavaのormで使うxmlをパースする際に使った関数です。
xmlの中身はこのようなものです。
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd"> <sqlMap namespace="jp.hoge.com"> <select id="selectId" resultClass="Sample" parameterClass="string"> SELECT id FROM sample WHERE id = 1 </select> </sqlMap>
このxmlをパースしてステートメントごとに配列にしています。
def get_statements(tree): statement_elements = [] for element in tree.iter(): if element.tag in ['select', 'insert', 'update', 'delete', 'procedure']: statement_elements.append(element) return statement_elements
属性を取得する際はelement.get('id')
のようにします。
tree.iter()
でelementを取得できますが、少し癖があります。
深さ優先探索のようにタグを取得していくので、書くのが難しかったです。
一度に欲しい情報を取得するのではなく、個別に取得する方法を採用しました。
例えば、以下のようなxmlがあった場合、sqlをdictに格納して、selectをパースしている最中にincludeを見つけたらdictを参照して、中身を置き換えます。
<sqlMap namespace="jp.hoge.com"> <sql id="sampleInclude"> where id = 2 </sql> <select id="selectId" resultClass="Sample" parameterClass="string"> SELECT id FROM sample <include refid="sampleInclude" /> </select> </sqlMap>