知財のあれこれをPythonで何とかする

### キーワード検索を何とかする
商用DBでもJ-Platpatでも一度リスト化したものを後でまた別の条件で絞り込みたい、ということはよくあるのではないでしょうか。今回はいったんExcel形式でリスト化した特許リストを別のキーワードで絞り込むための手法を紹介したいと思います。

今回のツールは以下のような条件で絞り込みを行うためのツールとなります。
（キーワード１　or　キーワード２）and(キーワード３　or　キーワード４)and・・・・
or条件で並べられるキーワード数とand条件の数が自由に設定できることがポイントです。

今回の例ではJ-PlatpatでダウンロードしたCSVファイルを処理するように作成しています。



```program1.py
#part1 検索キーワードを含む行を抽出
import openpyxl
mtchnum =17 #マッチ数の列を設定
target =8 #サーチ対象（要約）の列を設定
srchwrd =[["アプリケーション","ＯＳ"]]#検索ワードの設定

wb =openpyxl.load_workbook("特実_国内文献.xlsx")#検索対象のリストを読み出す
sheet = wb["Sheet1"] 

sheet.cell(row=1, column=mtchnum,value = "マッチ数")

index=0
for ORwd_gr in srchwrd:
    for y in range(2,sheet.max_row+1): # forループ終了条件：シート最終行+1
        for ORwd in ORwd_gr:
            if ORwd in str(sheet.cell(row=y, column=target).value) : #ORwdの文字列が要約に有ったらマッチ数の列の右側にORwdを書き込む
                sheet.cell(row=y, column=mtchnum+index+1,value = ORwd )
        sheet.cell(row=y, column=mtchnum,value = 0)#マッチ数の列を初期化する
    index +=1

#part2　マッチ数をカウントする
for y in range(2,sheet.max_row+1): # forループ終了条件：シート最終行+1
    for x in range(mtchnum+1,mtchnum+index+1):
        if sheet.cell(row=y, column=x).value is not None: #文字列があればmtchnum＝+１
            a = sheet.cell(row=y, column=mtchnum).value
            sheet.cell(row=y, column=mtchnum,value = a+1)
        
wb.save("srch_temp.xlsx")#検索結果の中間ファイルを保存

#part3　全ての検索条件を満たす行のみ抽出
import pandas as pd
df1 = pd.read_excel("srch_temp.xlsx")
df2 = df1[df1["マッチ数"] == index ] #indexと等しい行だけ残す
df2.to_excel("特実_国内文献srch.xlsx", index = False)

```

サンプルデータとしてJ-Platpatからのダウンロード形式の特許リストを添付しています。サンプルデータをメモ帳にコピペして文字コード「ANSI」で保存しファイル名を「特実_国内文献.csv」にして、xlsx形式に変換してから保存しProgram1を実行してください。以下のProgram2でcsv→xlsx変換ができます。

<details><summary><b>サンプルデータ</b></summary>

```rb
文献番号,出願番号,出願日,公知日,発明の名称,出願人/権利者,FI,要約,公開番号,公告番号,登録番号,審判番号,その他,ステージ,イベント詳細,文献URL
特開2022-123456,特願2021-625412,2021/06/04,2022/12/15,セキュリティ情報調査システム,N電気,"G06F21/56,340,G06N20/00,160,G06N3/08","マルウェアに関する情報の調査を効率よく行う。

",特開2022-123456,,特許6352458,,,特許 有効,登録公報の発行,https://www.j-platpat.inpit.go.jp/c1801/PU/JP-2022-123456/11/ja
特開2022-112233,特願2022-965482,2022/06/28,2022/08/26,マルウェア解析方法,"N電気,AP計算機",G06F21/55,マルウェアをより容易に解析できるＯＳ,特開2022-112233,,特許8652365,,,特許 有効,登録公報の発行,https://www.j-platpat.inpit.go.jp/c1801/PU/JP-2022-112233/11/ja
特開2022-012345,特願2022-032612,2022/02/15,2022/06/24,マルウェア検出のためのコンテキストプロファイリング,"N電気,AP計算機","G06F21/55,320,G06F21/53",マルウェアを特定して軽減するアプリケーションクラウド,特開2022-012345,,特許9652354,,,特許 有効,登録公報の発行,https://www.j-platpat.inpit.go.jp/c1801/PU/JP-2022-012345/11/ja
特開2022-098765,特願2021-258634,2021/12/06,2022/06/17,サーバーリソースを用いたインターネット装置,"Ｒ事務機,IB事務機","G06F21/56,G16Y40/50,G16Y30/10,H04N1/00",マルウェアの脅威からモノのインターネット装置を保護するアプリケーションサーバ,特開2022-098765,,特許6985246,,,特許 有効,登録公報の発行,https://www.j-platpat.inpit.go.jp/c1801/PU/JP-2022-098765/11/ja
特開2022-050403,特願2022-036987,2022/01/13,2022/04/12,通信システム,"Ｒ事務機,IB事務機","G06F21/56,H04L67/141,H04L67/143","オープンネットワーク内の通信端末に適切に提供可能な通信システムを提供する。
",特開2022-050403,,特許8654213,,,特許 有効,登録公報の発行,https://www.j-platpat.inpit.go.jp/c1801/PU/JP-2022-050403/11/ja
特開2022-332211,特願2022-236589,2022/01/14,2022/03/30,検出方法、検出プログラムおよび検出装置,Ｒ事務機,"G06F21/55,320,G06F21/56","マルウェアが、未知マルウェアであっても検出を行なう
",特開2022-332211,,特許6325684,,,特許 有効,登録公報の発行,https://www.j-platpat.inpit.go.jp/c1801/PU/JP-2022-332211/11/ja
特開2022-012321,特願2020-365248,2020/07/01,2022/01/17,情報処理システム、画像形成装置、情報処理方法、およびプログラム,Ｒ事務機,"G06F21/56,H04N1/00,127@A,B41J29/38,303",システムの動作を維持させるサーバ,特開2022-012321,,特許7523652,,,特許 有効,登録公報の発行,https://www.j-platpat.inpit.go.jp/c1801/PU/JP-2022-012321/11/ja
```
</details>

```program2.py
# CSV→xlsx変換
import pandas as pd
df = pd.read_csv("特実_国内文献.csv", encoding="shift-jis")
df.to_excel("特実_国内文献.xlsx",index=False)
```

最初の検索ワードを以下のように設定しています。program1を実行すると、"アプリケーション"　or　"ＯＳ"で要約のセルにキーワードがあるかのOR検索をします。

```py
srchwrd =[["アプリケーション","ＯＳ"]]
```

プログラムを実行すると検索結果が「特実_国内文献srch.xlsx」というファイルとして生成・保存されますので、Excelで開いてください。
(実行結果)

![１－１.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3831931/c7a8b01f-1f6c-e2c6-44af-951c1af2ced2.png)
要約に'アプリケーション','ＯＳ'のいずれかがある行(マッチ数＝１、Q列）が表示されました。
処理中に生成する中間ファイル（srch_temp.xlsx）では以下のようになっています。
![１ー２.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3831931/e15c7d34-8f65-0a37-696f-8f5b54bc3672.png)
中間ファイルではマッチしなかった行も残しています。

次に以下のように検索条件を増やし、AND検索としてみます。
```py
srchwrd =[["アプリケーション","ＯＳ"],
          ["クライアント","サーバ"]]
```
この検索条件では、("アプリケーション" or "ＯＳ") と("クライアント" or "サーバ")のAND検索になります。
上記の様に検索条件を変えて実行すると以下のようになります
（特実_国内文献srch.xlsxとsrch_temp.xlsxを開いたままprogram1を実行するとエラーになるので実行前に閉じておいて下さい）。
(実行結果)
![２－１.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3831931/af57f739-84ba-2d47-dc8e-b53d1e646c93.png)


マッチ数(Q列目)=2の行だけを表示します。これは、srchwrdのorキーワードのグループ数（検索条件のリストsrchwrdの行数）が「2行」であることからorとandの条件をすべて満たす行として表示しています。
中間ファイル（srch_temp.xlsx）では以下のようになっています。
![２－２.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3831931/455c3cf9-ff16-fc4e-7fb2-bef562baafaa.png)

このようにsrchwrdを変更することでキーワードを自由に設定できます。右方向にキーワードを増やせばOR条件として増やすことができますし、下方向に行数を増やせばAND条件として増やすことができます。

### part1（検索キーワードを含む行を抽出）の説明
part1の基本処理部分を以下に示します。
```py
index=0
for ORwd_gr in srchwrd:
    for y in range(2,sheet.max_row+1): # forループ終了条件：シート最終行+1
        for ORwd in ORwd_gr:
            if ORwd in str(sheet.cell(row=y, column=target).value) : #ORwdの文字列が要約に有ったらマッチ数の列の右側にORwdを書き込む
                sheet.cell(row=y, column=mtchnum+index+1,value = ORwd )
        sheet.cell(row=y, column=mtchnum,value = 0)#マッチ数の列を初期化する
    index +=1
```
流れとしては、リストsrchwrdをor条件のグループORwd_grに分け(1階層目のfor文)、
シートの最終行までキーワード検索を実行（2階層目のfor文)、
or条件のグループORwd_grを個別の検索キーワードORwdに分け(3階層目のfor文)、
次に、if ORwd in・・でシートの要約セルにORwdが有ったら、
y行のmtchnum+index+1列にORwdを書き込むようにしています。mtchnumはマッチ数を書き込む列、indexは何個目のORwd_grかを示しています。（ちなみに、もし要約セルに複数の種類のORwdが有ったら、最初のORwdは後のORwdに上書きされます)
マッチ数の列は次のpart2の処理に備えて初期値(0)を入れておきます。
3階層目のループはORwdごとにループし最後のORwdに到達した時点で終了します。
2階層目のループは変数yがシートの最終行に到達した時点で終了します。
1階層目のループはORwd_grごとにループし最後のORwd_grに到達した時点で終了します。

### part2の説明
part2の基本処理部分を以下に示します。
```py
#part2　マッチ数をカウントする
for y in range(2,sheet.max_row+1): # forループ終了条件：シート最終行+1
    for x in range(mtchnum+1,mtchnum+index+1):
        if sheet.cell(row=y, column=x).value is not None: #文字列があればmtchnum＝+１
            a = sheet.cell(row=y, column=mtchnum).value
            sheet.cell(row=y, column=mtchnum,value = a+1)
```
流れとしては、シートの最終行までマッチ数の加算を実行（1階層目のfor文)、
複数のORwd_grの個数だけ開始値mtchnum+1でループする(2階層目のfor文)、
part1の処理で検索ヒットするとmtchnum+index+1列にヒットした検索キーワードが書き込まれているので、書き込まれていればmtchnumの列にマッチ数1を加算します。（mtchnumの列の数値をいったん読み出して変数aに代入、aに1を加算してセルに上書きする)

### part3の説明
part3の基本処理部分を以下に示します。
```py
#part3　全ての検索条件を満たす行のみ抽出
import pandas as pd
df1 = pd.read_excel("srch_temp.xlsx")
df2 = df1[df1["マッチ数"] == index ] #indexと等しい行だけ残す
df2.to_excel("特実_国内文献srch.xlsx", index = False)
```
part3ではpandasライブラリを用いて、マッチ数がindexと等しい行すなわちorとandの条件をすべて満たす行のみ抽出して出力ファイル「特実_国内文献srch.xlsx」を書き出しています。

### paat1応用のポイント１
入力された「特実_国内文献.xlsx」のリストの右側（文献URLより右、Q列～)にヒットした検索キーワードを追加するため、mtchnum をQ列（列数17)にしています。他の形式のリストを検索したい場合は検索したい特許リストの形式に合わせてリストの右側にmtchnumを設定するようにしてください。
```py
mtchnum =17 #マッチ数の列を設定
```
part1で検索ワードの検索対象としている「要約」の列は8列目(H列)ですので、検索したいテキストがある列を変更したいときはtargetの数値を変更してください。
```py
target =8 #サーチ対象（要約）の列を設定
```

### part1応用のポイント２
本プログラムは検索対象のセル列は1つ(要約だけ)しかできません。それ以外のセル列も同時に検索したい場合は少し工夫する必要があります。
例えば要約とFIの２つを検索したい場合は、program1.pyのpart1（3~8行）
を以下のように書き換えてFIの検索用のプログラム（program1a.py）を作ります。
```(書き換え個所)program1a.py
mtchnum =20 #マッチ数の列を新たにT列に設定
target =7 #サーチ対象（FI）の列を設定
srchwrd =[["G06F21/56","H04N1/00"]]

wb =openpyxl.load_workbook("srch_temp.xlsx")
sheet = wb["Sheet1"] 
```
program1.pyを実行した後にprogram1a.pyを実行すると、中間ファイルsrch_temp.xlsxは以下のようになります。
![３－１.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3831931/a3bc3ea7-ad88-d3f6-400a-d57263a88e94.png)
以上のように別のプログラムで中間ファイルをさらに処理することで中間ファイルには検索結果が残っていきますので、excel上でマッチ数の列（Q列、T列）にフィルタをかければ絞り込みができます。例えばQ列を「２」、T列「１」で絞り込むと要約とFIの検索条件全てを満たす行が絞り込めます。

### おまけ
設定するキーワードが多くなってくるとダブルクオーテーションマーク＋カンマ形式（"～","～"）のテキストデータを作成するのが面倒になってきます。それでWindows11のCopilotに手伝ってもらって「Excel→txt変換プログラム」を作りました。キーワードはExcelで編集しファイル名srchwrd.xlsxで保存し、以下のプログラムprogram3.pyを実行するとダブルクオーテーションマーク＋カンマ形式でoutput.txtが保存されます。
```program3.py
import pandas as pd
import os

# Excelファイルを読み込む
excel_file = 'srchwrd.xlsx'
df = pd.read_excel(excel_file)

# CSVファイルとして保存する際にダブルクオーテーションで囲む
csv_file = 'output.csv'
df.to_csv(csv_file, index=False, quoting=1)  # quoting=1 は csv.QUOTE_ALL に相当

# 拡張子をtxtに変更する
os.rename('output.csv','output.txt')
```

### 最後に
pythonの「for文を用いたリスト処理」の機能を使うと、リスト(srchwrd)として設定した要素(ORwd_gr、ORwd)の個数をfor文のループ処理の回数として処理するようにできるので、今回のようなキーワードの個数や行数が変化するような場合でも非常に短いプログラムでキーワード検索を実現できることが実感していただけると思います。
今回の記事がお役に立てれば幸いです。