[Python] 288 CSVファイルから改行コードを削除

一定の頻度で必要になりそうなので書いておきます。

import csv

print("開始年度を入力してください")
start_year = input()

print("終了年度を入力してください")
end_year = input()

for year in range(int(start_year),int(end_year)+1):
    file = f'{year}.csv'
    file_new= f'{year}_new.csv'

    with open (file, mode="r", encoding="UTF-8") as f1:
        with open(file_new, mode="w", encoding="UTF-8") as f2:
            writer = csv.writer(f2)
            for row in csv.reader(f1):
                rows = []
                for i in range(0,len(row)):
                    rows.append(row[i].replace('\n',''))
                writer.writerow(rows)

[Python] 287 引数ありのC言語モジュール ハッシュ関数 FNV

Pythonでもハッシュ関数FNVを使えるようにするためC言語モジュール化しました。

モジュールからの戻り値は整数の範囲でしか使えず、32ビットのunsigned intには対応できましたが、64ビットではうまくいきませんでした。

初めはPythonからポインタをどうやって渡すのか見当もつかなくて、戻り値の受け取り方もかなり試行錯誤しました。

これでC言語とPythonで同一文字列から同じハッシュ値を作成できるので色々使い道がありそうです。

2021/7/25追記:コードを改良して以下の記事にアップしています。

#define PY_SSIZE_T_CLEAN
#include <Python.h>

extern uint32_t fnv_1_hash_32(const char*);

static PyObject* fnv_32(PyObject* self, PyObject* args)
{
    const char* s;
    unsigned int hash=2166136261U;

    if (!PyArg_ParseTuple(args, "s", &s)){
        return NULL;
    }
    else{
        while (*s) {
        hash*=16777619U;
        hash^=*(s++);
        }

        return Py_BuildValue("i", hash);
    }
}

static PyMethodDef fnvmethods[] = {
    {"fnv_1_hash_32", fnv_32, METH_VARARGS},
    {NULL,NULL,0}
};

static struct PyModuleDef fnv = {
    PyModuleDef_HEAD_INIT,
    "fnv",
    "Python3 C API Module(Sample 1)",
    -1,
    fnvmethods
};

PyMODINIT_FUNC PyInit_fnv(void)
{
    return PyModule_Create(&fnv);
}
from c_module import fnv

name = 'シャフリヤール'

hash = fnv.fnv_1_hash_32(name)

# int から unsigned int 32bitへ変換
hash_unsigned32 = hash & 0xffffffff

print(hash_unsigned32)
--------------------------------------------------

出力
--------------------------------------------------
3687658616
#include <stdio.h>
#include <stdint.h>

uint32_t fnv_1_hash_32(char *s)
{
    unsigned int hash=2166136261U;

    while (*s) {
        hash*=16777619U;
        hash^=*(s++);
    }
    return hash;
}
from distutils.core import setup, Extension

setup(name='fnv',
    version='1.0',
    ext_modules=[Extension('fnv', sources = ['fnv.c','fnv_1.c'])]
)
<セットアップコマンド>

・自作ライブラリに配置するsoファイルを作成するコマンド "from c_module import fnv"
python setup.py build_ext -i

・既存のライブラリにインストールするコマンド "import fnv"
python setup.py install

[Python] 286 C言語実行ファイルの併用 その4 文字列のハッシュ化

これまでC言語を学習してきて、この言語は文字列の取り扱いが不得手というのがよく分かりました。そのような仕様でもstrcmp関数は1文字づつの比較しかできないながら相当速くなっていると思います。

さらなる高速化を検討した結果、文字列を数字に変換して加減計算等できるようにすれば処理が速くなるのではと考え、ハッシュ関数によるハッシュ化を試してみました。

データベースに馬名のハッシュ値を格納し検索に用いたところ、24000件の処理を80秒から55秒に短縮しました。

sscanf関数が扱いづらくatoi関数の必要性にもなかなか気がつかなかったため、かなり難航しました。

今回は比較する文字列の片方を前もってハッシュ化しましたが、両方処理していたらさらに速くなるはずです。

<修正箇所>
uint32_t horse_hash_int;

while(fgets(buf,2000,fp ) != NULL ) {
    if (i != 0){
        ret = sscanf(buf, " %[^,],%[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %s",horseID,horse_hash,horse_name,horse_name0,status,gender,hair,birthday,trainer,owner,info,breeder,area,price,prize_money,result,wining_race,relatives) ;
	
        horse_hash_int = atoi(horse_hash);
		
        if(horse_hash_int - fnv_1_hash_32(horse_name_in) == 0){
            fp3 = fopen(fname3, "a");
            fprintf(fp3," %s,%9s\n",horse_name_in,horseID);
            fclose(fp3);
            b ++;
            break;
        }
    }
    i ++ ;
}

[C言語] 06 ハッシュ関数 FNVの外部関数化

FNVをPythonでも使えるようにするため、まずは外部関数化しました。

64バイトハッシュ値のフォーマット指定子は%luです。

#include <stdio.h>
#include <stdint.h>

uint32_t fnv_1_hash_32(char *s)
{
    unsigned int hash=2166136261U;

    while (*s) {
        hash*=16777619U;
        hash^=*(s++);
    }
    return hash;
}

uint64_t fnv_1_hash_64(char *s)
{
    long unsigned int hash=14695981039346656037U;

    while (*s) {
        hash*=1099511628211LLU;
        hash^=*(s++);
    }
    return hash;
}
#include <stdio.h>
#include <stdint.h>
#include "fnv_1.c"

int main(void)
{
    char *s = "シャフリヤール";

    printf("%u\n",fnv_1_hash_32(s));
    printf("%lu\n",fnv_1_hash_64(s));
}
--------------------------------------------------

出力
--------------------------------------------------
3687658616
7203286604922561048

[C言語] 05 簡易なハッシュ関数 FNV

[Python] 283の記事あたりまで取り組んでいたデータベース検索の高速化ですが、文字列比較ではなくハッシュ値比較でも試してみました。

ハッシュ関数によるハッシュ値算出の時間が余計に掛かっているものの、これまでの最速記録80秒に対して84秒となかなかの健闘でした。正確性も問題ありません。

その他では機械語変換手前のアセンブリ言語コードをチェックしたりもしましたが初級者にいじれるはずもなく、また最近の文字列比較機能(strcmpなど)は優秀のようで小手先の自作関数では歯が立ちませんでした。

ハッシュ関数についてはスキルアップのための余興みたいなもので、ここらで高速化検討は打ち切りにするつもりです。

#include <stdio.h>
#include <stdint.h>

uint32_t fnv_1_hash_32(char *s)
{
    unsigned int hash=2166136261UL;

    while (*s) {
        hash*=16777619UL;
        hash^=*(s++);
    }
    return hash;
}
<該当箇所のみ>
#include "fnv_1.c"

if (i != 0){
    if (fnv_1_hash_32(horse_name) - fnv_1_hash_32(horse_name_in) == 0){
        fp3 = fopen(fname3, "a");
        fprintf(fp3,"%s,%9s\n",horse_name_in,horseID);
        fclose(fp3);
        b ++;
        break;
    }
}

[Python] 286 スクレイピング速度検証 curlコマンド

色々検証してきましたが、htmlのダウンロードについてはRequestsやseleniumを知らなくてもcurlコマンド1つでできるようです。あとはBeautifulSoupで解析すればOKです。

処理時間は私の検証条件ではhtml解析を経てcsv作成まで1件あたり0.23sでした。Requestsやlibcurlが0.35sなのでcurlコマンドが最速になります。

コードも簡潔になりますし、htmlをダウンロードする場合は馴染みのあるRequestsではなくcurlコマンドを採用します。

ただし、これまでと同様ループする場合はtime.sleep等で速度を制御しないとスクレイピング先に迷惑がかかるので要注意です。

ダウンロードせずにhtml解析&テキスト取り出しであれば、これまで通りRequestsになります。

<該当箇所のみ>
proc = subprocess.run("curl 'スクレイピング先のurl' > [出力先ファイル] ; echo '出力完了'", shell=True, stdout= subprocess.PIPE, stderr = subprocess.PIPE)
print(proc.stdout.decode('UTF-8'))

with open(出力先ファイル,encoding='EUC-JP',errors='ignore') as f:
    contents= f.read()
soup = BeautifulSoup(contents, "html.parser")

[Python] 285 スクレイピング速度検証 Requests

前回の続きです。

Requests+BeautifulSoupでスクレイピング の速度を測ったところlibcurlと同等でした。

通常はRequests、クリックやページ遷移などブラウザ操作する場合はselenium、好事家はlibcurlというのが結論です。

<該当箇所のみ>
response = requests.get(url)

soup = BeautifulSoup(response.text.encode('utf-8'), "html.parser")

[Python] 284 C言語併用によるスクレイピング高速化 libcurl

seleniumの動きがもっさりしているため、C/C++のlibcurlというライブラリで高速化を試みました。スクレイピング先のhtmlをまるごとダウンロードして解析し、テキストを抽出してcsvファイルにまとめるという流れです。

その結果、こちらが引くくらい速くなったので全編C言語で書く必要はなくなり、html解析はPythonライブラリのBeautifulSoupで実施しました。スクレイピング先に負荷をかけないようtime.sleepでループの速度を遅くしています。

C言語の方は参考サイトのコードに少しだけ手を入れて完成させました。

今思えばここまでやらなくてもRequests + BeautifulSoupであればPythonだけで高速化できていたかもしれません。時間があれば検証します。

21/07/17追記:C言語にこだわらなければ、以下のコマンドをsubprocessモジュールで走らせてhtmlをダウンロードするのが最も簡単だと思います。
curl “スクレイピング先のurl” > 出力先

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>

#define EXIT_SUCCESS 0

FILE *fp; // 入力ファイル
FILE *fp2; // 出力ファイル
char fname[] = "horse_url.txt";
char fname2[] = "curl.html";
char buffer[100];
char horse_url[100];

struct Buffer {
    char *data;
    int data_size;
};

size_t buffer_writer(char *ptr, size_t size, size_t nmemb, void *stream) {
    struct Buffer *buf = (struct Buffer *)stream;
    int block = size * nmemb;
    if (!buf) {
        return block;
    }

    if (!buf->data) {
        buf->data = (char *)malloc(block);
    }
    else {
        buf->data = (char *)realloc(buf->data, buf->data_size + block);
    }

    if (buf->data) {
        memcpy(buf->data + buf->data_size, ptr, block);
        buf->data_size += block;
    }

    return block;
}

int main(void) {

    CURL *curl;
    struct Buffer *buf;

    buf = (struct Buffer *)malloc(sizeof(struct Buffer));
    buf->data = NULL;
    buf->data_size = 0;

    fp = fopen(fname, "r");
	while(fgets(buffer,100, fp) != NULL ) {
        sscanf(buffer,"%s",horse_url);
    }
    fclose(fp);

    curl = curl_easy_init();
    curl_easy_setopt(curl, CURLOPT_URL, horse_url);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, buf);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, buffer_writer);

    curl_easy_perform(curl);
    curl_easy_cleanup(curl);

    fp2 = fopen(fname2, "w");
    fprintf(fp2, "%s", buf->data);
    fclose(fp2);

    free(buf->data);
    free(buf);

    return EXIT_SUCCESS;
}
<C言語コンパイル&実行とBeautifulSoup処理の箇所のみ>

proc = subprocess.run("gcc [ソースコード] -I/usr/local/opt/curl/include -lcurl ; ./a.out ; ECHO 'C言語実行完了'", shell=True, stdout= subprocess.PIPE, stderr = subprocess.PIPE)
print(proc.stdout.decode('UTF-8'))

with open(html,encoding='EUC-JP',errors='ignore') as f:
    contents= f.read()
soup = BeautifulSoup(contents, "html.parser")

参考サイト

[Python] 283 C言語実行ファイルの併用 その3 若干の時間短縮

前回の続きです。

どう考えても検索ヒットした馬名のデータが2行になるのは無駄なので、1行になるよう修正しました。

この修正により処理時間が87秒から80秒に短縮されました。

<修正箇所>

while(fgets(buf,2000, fp ) != NULL ) {
    sscanf(buf, " %9s, %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %s",horseID,horse_name,horse_name0,status,gender,hair,birthday,trainer,owner,info,breeder,area,price,prize_money,result,wining_race,relatives ) ;

    if (i != 0){
        if (strcmp(horse_name,horse_name_in)==0){
	        fp3 = fopen(fname3, "a");
	        fprintf(fp3, "%s,%9s\n", horse_name,horseID);
	        fclose(fp3);
	        b ++;
	        break;
        }
    }
    i ++ ;
}
if (b == 0){
	fp3 = fopen(fname3, "a");
	fprintf(fp3, "%s,100000000\n", horse_name_in);
	fclose(fp3);
}

[Python] 282 C言語実行ファイルの併用 その2 内容説明

前回の続きです。

C言語実行から作成したデータを処理する流れを説明します。

実行ファイルはsubprocessモジュールで走らせて、処理の終了は実行ファイルの最後に仕込んだ標準出力の受け取りで判断します。

作成されたデータ内容は下図のようになっています。データベースにヒットした馬名は2行になり、ヒットしなかった馬名は1行になります。

あとはpandasなどを使ってデータ加工するのですが、重複行の削除には一工夫必要でした。馬名は複数回登場することがあるのでpandasのduplicatedメソッドは使えず、馬名列要素のリストと1要素分スライドしたリストを比較し重複する所のインデックス番号をリスト化して処理しました。

重たい処理はC言語にさせて、出来上がったラフなデータをPythonで加工する。データベースを扱うに際し私にとって最強の組み合わせになりそうです。

<該当箇所のみ>

# C言語実行ファイル
proc = subprocess.run(C言語実行ファイルのパス, shell=True, stdout= subprocess.PIPE, stderr = subprocess.PIPE)
print(proc.stdout.decode('UTF-8'))

# horseID.csvのデータフレーム化
df = pd.read_csv(horseID_file,names=['馬名2','horseID'],encoding='UTF-8')

# "馬名2"列リストと1要素スライドしたリストを作成(最後の'A'は数合わせ)
list_horseA = df['馬名2'].tolist()
list_horseB = list_horseA[1:] + ['A']

# 重複行のインデックス番号を取得してリスト化
list_num = []
i = 1
for nameA,nameB in zip(list_horseA,list_horseB):
    if nameA == nameB:
        list_num.append(i)
    i = i + 1

# 重複行を削除
df2 = df.drop(df.index[list_num])