├── boogle ├── static │ └── logo.png ├── main.py └── templates │ └── index.html ├── elasticsearch101.pdf ├── README.md ├── .gitignore ├── 01_mysql.ipynb ├── 03_search_engine.ipynb └── 02_elastic_search.ipynb /boogle/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejungwon/search-engine-tutorial/HEAD/boogle/static/logo.png -------------------------------------------------------------------------------- /elasticsearch101.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejungwon/search-engine-tutorial/HEAD/elasticsearch101.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Search Engine Tutorial 2 | 3 | ## Slide 4 | 5 | - [Elasticsearch 101](elasticsearch101.pdf) 6 | 7 | ## Installation 8 | 9 | ### 1. MySQL 10 | 11 | ``` 12 | apt-get install mysql-server -y 13 | service mysql restart 14 | mysql_secure_installation 15 | ``` 16 | 17 | ``` 18 | root@88f2909b2200:~# mysql_secure_installation 19 | 20 | ... 21 | Press y|Y for Yes, any other key for No: y 22 | ... 23 | Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 0 24 | ... 25 | New password: Boost111@ 26 | Re-enter new password: Boost111@ 27 | ... 28 | 29 | Do you wish to continue with the password provided?(Press y|Y for Yes, any other key for No) : y 30 | ... 31 | Remove anonymous users? (Press y|Y for Yes, any other key for No) : y 32 | ... 33 | Disallow root login remotely? (Press y|Y for Yes, any other key for No) : No 34 | ... 35 | Remove test database and access to it? (Press y|Y for Yes, any other key for No) : No 36 | ... 37 | Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y 38 | ``` 39 | 40 | ### 2. Elasticsearch 41 | 42 | ``` 43 | apt-get update && apt-get install -y gnupg2 44 | wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | apt-key add - 45 | apt-get install apt-transport-https 46 | echo "deb https://artifacts.elastic.co/packages/7.x/apt stable main" | tee /etc/apt/sources.list.d/elastic-7.x.list 47 | apt-get update && apt-get install elasticsearch 48 | service elasticsearch start 49 | cd /usr/share/elasticsearch 50 | bin/elasticsearch-plugin install analysis-nori 51 | service elasticsearch restart 52 | 53 | ``` 54 | -------------------------------------------------------------------------------- /boogle/main.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask import request,send_from_directory 3 | from flask import render_template,url_for 4 | from flask import Response 5 | import json 6 | from datetime import datetime 7 | import time 8 | import mysql.connector 9 | from elasticsearch import Elasticsearch 10 | INDEX_NAME = "movie_index" 11 | app = Flask(__name__, static_url_path='/static', 12 | static_folder='static') 13 | 14 | 15 | 16 | 17 | HOST = "localhost" 18 | USER = "root" 19 | PASSWORD = "Boost111@" 20 | 21 | 22 | 23 | def get_data(keyword): 24 | try: 25 | es.transport.close() 26 | except: 27 | pass 28 | es = Elasticsearch() 29 | mydb = mysql.connector.connect( 30 | host=HOST, 31 | user=USER, 32 | password=PASSWORD, 33 | database="movie_db" 34 | ) 35 | mycursor = mydb.cursor(dictionary=True) 36 | 37 | res = es.search(index=INDEX_NAME, q=keyword) 38 | movie_ids = [] 39 | for data in res['hits']['hits']: 40 | movie_ids.append(data["_id"]) 41 | 42 | sql = "SELECT * FROM movie WHERE id IN {} ORDER BY FIELD (id, {})".format(tuple(movie_ids),','.join(movie_ids)) 43 | 44 | mycursor.execute(sql) 45 | 46 | query_result = [] 47 | myresult = mycursor.fetchall() 48 | for result in myresult: 49 | page = { 50 | "link":result["link"], 51 | "image":result["image"], 52 | "title":result["title"], 53 | "story":result["story"], 54 | } 55 | query_result.append(page) 56 | es.close() 57 | mycursor.close() 58 | mydb.close() 59 | return query_result 60 | 61 | 62 | @app.route('/') 63 | def index(): 64 | return render_template("index.html") 65 | 66 | @app.route('/search', methods=['POST']) 67 | def search(): 68 | keyword = request.values.get('data') 69 | res = get_data(keyword) 70 | 71 | return Response(json.dumps(res), mimetype='application/json') 72 | 73 | 74 | if __name__ == '__main__': 75 | app.run(host="0.0.0.0", port=6006, debug=True) 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | .DS_Store -------------------------------------------------------------------------------- /boogle/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 39 | 40 | 41 |
42 | 48 |
49 |

검색어를 입력해주세요

50 |
51 | 62 |
63 |
64 |
65 | 88 | 89 | -------------------------------------------------------------------------------- /01_mysql.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# MySQL\n", 8 | "데이터를 데이터베이스에 저장한 뒤에, SQL 쿼리를 활용해 출력을 해보겠습니다.\n", 9 | "____" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 3, 15 | "metadata": {}, 16 | "outputs": [ 17 | { 18 | "name": "stdout", 19 | "output_type": "stream", 20 | "text": [ 21 | "Collecting mysql-connector\n", 22 | " Downloading mysql-connector-2.2.9.tar.gz (11.9 MB)\n", 23 | "\u001b[K |████████████████████████████████| 11.9 MB 14.6 MB/s eta 0:00:01\n", 24 | "\u001b[?25hBuilding wheels for collected packages: mysql-connector\n", 25 | " Building wheel for mysql-connector (setup.py) ... \u001b[?25ldone\n", 26 | "\u001b[?25h Created wheel for mysql-connector: filename=mysql_connector-2.2.9-cp38-cp38-linux_x86_64.whl size=247947 sha256=c831d075fe9723839a8e6b8776c85774af4eb9c19168ef31bcb7abe3817dfaea\n", 27 | " Stored in directory: /opt/ml/.cache/pip/wheels/57/e4/98/5feafb5c393dd2540e44b064a6f95832990d543e5b4f53ea8f\n", 28 | "Successfully built mysql-connector\n", 29 | "Installing collected packages: mysql-connector\n", 30 | "Successfully installed mysql-connector-2.2.9\n" 31 | ] 32 | } 33 | ], 34 | "source": [ 35 | "!pip install mysql-connector" 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": { 41 | "tags": [] 42 | }, 43 | "source": [ 44 | "## 1. 데이터 불러오기" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": 1, 50 | "metadata": {}, 51 | "outputs": [ 52 | { 53 | "data": { 54 | "text/html": [ 55 | "
\n", 56 | "\n", 69 | "\n", 70 | " \n", 71 | " \n", 72 | " \n", 73 | " \n", 74 | " \n", 75 | " \n", 76 | " \n", 77 | " \n", 78 | " \n", 79 | " \n", 80 | " \n", 81 | " \n", 82 | " \n", 83 | " \n", 84 | " \n", 85 | " \n", 86 | " \n", 87 | " \n", 88 | " \n", 89 | " \n", 90 | " \n", 91 | " \n", 92 | " \n", 93 | " \n", 94 | " \n", 95 | " \n", 96 | " \n", 97 | " \n", 98 | " \n", 99 | " \n", 100 | " \n", 101 | " \n", 102 | " \n", 103 | " \n", 104 | " \n", 105 | " \n", 106 | " \n", 107 | " \n", 108 | " \n", 109 | " \n", 110 | " \n", 111 | " \n", 112 | " \n", 113 | " \n", 114 | " \n", 115 | " \n", 116 | " \n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | "
idtitlestorygenderlinkimage
00인비저블 게스트의문의 습격으로 살해 당한 ‘로라’ ‘아드리안’은 연인의 죽음에 절망하고, 범인은 ...Nhttps://movie.naver.com/movie/bi/mi/basic.nhn?...https://movie-phinf.pstatic.net/20170828_179/1...
11나, 다니엘 블레이크평생을 성실하게 목수로 살아가던 다니엘은 지병인 심장병이 악화되어 일을 계속 해나갈...Fhttps://movie.naver.com/movie/bi/mi/basic.nhn?...https://movie-phinf.pstatic.net/20161117_246/1...
22국가부도의 날1997년, 대한민국 최고의 경제 호황을 믿어 의심치 않았던 그때, 곧 엄청난 경제...Mhttps://movie.naver.com/movie/bi/mi/basic.nhn?...https://movie-phinf.pstatic.net/20181105_232/1...
33당갈전직 레슬링 선수였던 ‘마하비르 싱 포갓(아미르 칸)’은 아버지의 반대로 금메달의 ...Fhttps://movie.naver.com/movie/bi/mi/basic.nhn?...https://movie-phinf.pstatic.net/20180329_2/152...
44스파이더맨: 파 프롬 홈‘엔드게임’ 이후 변화된 세상, 스파이더맨 ‘피터 파커’는 학교 친구들과 유럽 여행...Mhttps://movie.naver.com/movie/bi/mi/basic.nhn?...https://movie-phinf.pstatic.net/20190527_181/1...
\n", 129 | "
" 130 | ], 131 | "text/plain": [ 132 | " id title story \\\n", 133 | "0 0 인비저블 게스트 의문의 습격으로 살해 당한 ‘로라’ ‘아드리안’은 연인의 죽음에 절망하고, 범인은 ... \n", 134 | "1 1 나, 다니엘 블레이크 평생을 성실하게 목수로 살아가던 다니엘은 지병인 심장병이 악화되어 일을 계속 해나갈... \n", 135 | "2 2 국가부도의 날 1997년, 대한민국 최고의 경제 호황을 믿어 의심치 않았던 그때, 곧 엄청난 경제... \n", 136 | "3 3 당갈 전직 레슬링 선수였던 ‘마하비르 싱 포갓(아미르 칸)’은 아버지의 반대로 금메달의 ... \n", 137 | "4 4 스파이더맨: 파 프롬 홈 ‘엔드게임’ 이후 변화된 세상, 스파이더맨 ‘피터 파커’는 학교 친구들과 유럽 여행... \n", 138 | "\n", 139 | " gender link \\\n", 140 | "0 N https://movie.naver.com/movie/bi/mi/basic.nhn?... \n", 141 | "1 F https://movie.naver.com/movie/bi/mi/basic.nhn?... \n", 142 | "2 M https://movie.naver.com/movie/bi/mi/basic.nhn?... \n", 143 | "3 F https://movie.naver.com/movie/bi/mi/basic.nhn?... \n", 144 | "4 M https://movie.naver.com/movie/bi/mi/basic.nhn?... \n", 145 | "\n", 146 | " image \n", 147 | "0 https://movie-phinf.pstatic.net/20170828_179/1... \n", 148 | "1 https://movie-phinf.pstatic.net/20161117_246/1... \n", 149 | "2 https://movie-phinf.pstatic.net/20181105_232/1... \n", 150 | "3 https://movie-phinf.pstatic.net/20180329_2/152... \n", 151 | "4 https://movie-phinf.pstatic.net/20190527_181/1... " 152 | ] 153 | }, 154 | "execution_count": 1, 155 | "metadata": {}, 156 | "output_type": "execute_result" 157 | } 158 | ], 159 | "source": [ 160 | "import pandas as pd\n", 161 | "df = pd.read_csv(\"movie_doc.csv\",sep=\"\\t\")\n", 162 | "df.head()" 163 | ] 164 | }, 165 | { 166 | "cell_type": "markdown", 167 | "metadata": {}, 168 | "source": [ 169 | "## 2. MySQL을 활용한 데이터 저장" 170 | ] 171 | }, 172 | { 173 | "cell_type": "code", 174 | "execution_count": 2, 175 | "metadata": {}, 176 | "outputs": [], 177 | "source": [ 178 | "import mysql.connector" 179 | ] 180 | }, 181 | { 182 | "cell_type": "markdown", 183 | "metadata": {}, 184 | "source": [ 185 | "### 2-1. Database 생성" 186 | ] 187 | }, 188 | { 189 | "cell_type": "code", 190 | "execution_count": 4, 191 | "metadata": {}, 192 | "outputs": [], 193 | "source": [ 194 | "\n", 195 | "HOST = \"localhost\"\n", 196 | "USER = \"root\"\n", 197 | "PASSWORD = \"Boost111@\"\n", 198 | "mydb = mysql.connector.connect(\n", 199 | " host=HOST,\n", 200 | " user=USER,\n", 201 | " password=PASSWORD\n", 202 | ")\n", 203 | "\n", 204 | "mycursor = mydb.cursor()\n", 205 | "\n", 206 | "# mycursor.execute(\"DROP DATABASE movie_db\")\n", 207 | "try:\n", 208 | " mycursor.execute(\"CREATE DATABASE movie_db default character set utf8 collate utf8_general_ci;\")\n", 209 | "except:\n", 210 | " pass" 211 | ] 212 | }, 213 | { 214 | "cell_type": "markdown", 215 | "metadata": {}, 216 | "source": [ 217 | "### 2-2. Table 생성" 218 | ] 219 | }, 220 | { 221 | "cell_type": "code", 222 | "execution_count": 5, 223 | "metadata": {}, 224 | "outputs": [], 225 | "source": [ 226 | "mydb = mysql.connector.connect(\n", 227 | " host=HOST,\n", 228 | " user=USER,\n", 229 | " password=PASSWORD,\n", 230 | " database=\"movie_db\"\n", 231 | ")\n", 232 | "\n", 233 | "mycursor = mydb.cursor()" 234 | ] 235 | }, 236 | { 237 | "cell_type": "code", 238 | "execution_count": 6, 239 | "metadata": {}, 240 | "outputs": [], 241 | "source": [ 242 | "\n", 243 | "try:\n", 244 | " mycursor.execute(\"DROP TABLE movie;\")\n", 245 | "except:\n", 246 | " pass\n", 247 | "mycursor.execute(\"\"\"\n", 248 | " CREATE TABLE movie \n", 249 | " (id VARCHAR(255) UNIQUE, story TEXT, title VARCHAR(255), link VARCHAR(255), image VARCHAR(255))\"\"\"\n", 250 | " )\n" 251 | ] 252 | }, 253 | { 254 | "cell_type": "code", 255 | "execution_count": 7, 256 | "metadata": {}, 257 | "outputs": [ 258 | { 259 | "name": "stdout", 260 | "output_type": "stream", 261 | "text": [ 262 | "{'id': 0, 'title': '인비저블 게스트', 'story': '의문의 습격으로 살해 당한 ‘로라’ ‘아드리안’은 연인의 죽음에 절망하고, 범인은 흔적도 없이 사라졌다. 유력한 용의자로 누명을 쓴 ‘아드리안’은 승률 100%의 변호사 ‘버지니아’를 선임한다. 그리고 자신의 무죄를 입증하기 위해 고군분투하던 중 과거 그와 ‘로라’가 은폐한 교통사고와 숨겨진 연관성을 찾게 되는데… 남은 시간은 단 3시간, 사건을 재구성해 무죄를 입증해야 한다!', 'gender': 'N', 'link': 'https://movie.naver.com/movie/bi/mi/basic.nhn?code=159516', 'image': 'https://movie-phinf.pstatic.net/20170828_179/1503887362732snsIK_JPEG/movie_image.jpg?type=f67'}\n" 263 | ] 264 | } 265 | ], 266 | "source": [ 267 | "for row_dict in df.to_dict(orient=\"records\"):\n", 268 | " print(row_dict)\n", 269 | " break" 270 | ] 271 | }, 272 | { 273 | "cell_type": "markdown", 274 | "metadata": {}, 275 | "source": [ 276 | "### 2-3. Row 삽입" 277 | ] 278 | }, 279 | { 280 | "cell_type": "code", 281 | "execution_count": 8, 282 | "metadata": {}, 283 | "outputs": [], 284 | "source": [ 285 | "for row_dict in df.to_dict(orient=\"records\"):\n", 286 | "# print(row_dict)\n", 287 | " movie_id = str(row_dict['id'])\n", 288 | " title = row_dict['title']\n", 289 | " story = row_dict['story']\n", 290 | " link = row_dict['link']\n", 291 | " image = row_dict['image']\n", 292 | "\n", 293 | " sql = \"INSERT INTO movie (id, story, title, link, image) VALUES (%s, %s, %s, %s, %s)\"\n", 294 | " val = (movie_id, story, title, link, image)\n", 295 | " mycursor.execute(sql, val)\n", 296 | "\n", 297 | "mydb.commit()\n", 298 | "\n" 299 | ] 300 | }, 301 | { 302 | "cell_type": "markdown", 303 | "metadata": {}, 304 | "source": [ 305 | "### 2-4. 삽입된 Row 출력" 306 | ] 307 | }, 308 | { 309 | "cell_type": "code", 310 | "execution_count": 9, 311 | "metadata": {}, 312 | "outputs": [ 313 | { 314 | "name": "stdout", 315 | "output_type": "stream", 316 | "text": [ 317 | "{'id': '0', 'story': '의문의 습격으로 살해 당한 ‘로라’ ‘아드리안’은 연인의 죽음에 절망하고, 범인은 흔적도 없이 사라졌다. 유력한 용의자로 누명을 쓴 ‘아드리안’은 승률 100%의 변호사 ‘버지니아’를 선임한다. 그리고 자신의 무죄를 입증하기 위해 고군분투하던 중 과거 그와 ‘로라’가 은폐한 교통사고와 숨겨진 연관성을 찾게 되는데… 남은 시간은 단 3시간, 사건을 재구성해 무죄를 입증해야 한다!', 'title': '인비저블 게스트', 'link': 'https://movie.naver.com/movie/bi/mi/basic.nhn?code=159516', 'image': 'https://movie-phinf.pstatic.net/20170828_179/1503887362732snsIK_JPEG/movie_image.jpg?type=f67'}\n" 318 | ] 319 | } 320 | ], 321 | "source": [ 322 | "sql = \"SELECT * FROM movie\"\n", 323 | "mycursor = mydb.cursor(dictionary=True)\n", 324 | "mycursor.execute(sql)\n", 325 | "\n", 326 | "\n", 327 | "myresult = mycursor.fetchall()\n", 328 | "for x in myresult:\n", 329 | " print(x)\n", 330 | " break" 331 | ] 332 | }, 333 | { 334 | "cell_type": "code", 335 | "execution_count": 10, 336 | "metadata": {}, 337 | "outputs": [ 338 | { 339 | "name": "stdout", 340 | "output_type": "stream", 341 | "text": [ 342 | "{'id': '41', 'story': \"고도의 훈련을 받은 최고의 암살요원 제이슨 본. 사고로 잃었던 기억을 단편적으로 되살리던 제이슨 본은 자신을 암살자로 만든 이들을 찾던 중 ‘블랙브라이어’라는 존재를 알게 된다. ‘블랙브라이어’는 비밀요원을 양성해내던 '트레드스톤'이 국방부 산하의 극비조직으로 재편되면서 더욱 막강한 파워를 가지게 된 비밀기관. 그들에게 자신들의 비밀병기 1호이자 진실을 알고 있는 유일한 인물인 제이슨 본은 반드시 제거해야 하는 대상이다. 니키의 도움으로 블랙브라이어의 실체를 알게 된 제이슨 본은 런던, 마드리드, 모로코 그리고 뉴욕까지 전세계를 실시간 통제하며 자신을 제거하고 비밀을 은폐하려는 조직과 숨막히는 대결을 시작하는데…\", 'title': '본 얼티메이텀', 'link': 'https://movie.naver.com/movie/bi/mi/basic.nhn?code=59075', 'image': 'https://movie-phinf.pstatic.net/20160628_2/1467080136011unJpU_JPEG/movie_image.jpg?type=f67'}\n", 343 | "{'id': '67', 'story': '업계 최고의 레전드 킬러 ‘존 윅’은 과거를 뒤로한 채 은퇴를 선언하지만, 과거 자신의 목숨을 구했던 옛 동료와 피로 맺은 암살자들의 룰에 의해 로마로 향한다. ‘국제 암살자 연합’을 탈취하려는 옛 동료의 계획으로 ‘존 윅’은 함정에 빠지게 되고, 전세계 암살자들의 총구는 그를 향하는데...', 'title': '존 윅 - 리로드', 'link': 'https://movie.naver.com/movie/bi/mi/basic.nhn?code=143932', 'image': 'https://movie-phinf.pstatic.net/20200807_193/1596789703698oLgpX_JPEG/movie_image.jpg?type=f67'}\n" 344 | ] 345 | } 346 | ], 347 | "source": [ 348 | "\n", 349 | "keyword = \"암살자\"\n", 350 | "sql = \"SELECT * FROM movie WHERE story LIKE '%{}%'\".format(keyword)\n", 351 | "\n", 352 | "mycursor = mydb.cursor(dictionary=True)\n", 353 | "mycursor.execute(sql)\n", 354 | "\n", 355 | "\n", 356 | "myresult = mycursor.fetchall()\n", 357 | "for x in myresult:\n", 358 | " print(x)" 359 | ] 360 | }, 361 | { 362 | "cell_type": "markdown", 363 | "metadata": {}, 364 | "source": [ 365 | "### Q.여러키워드를 포함시키려면?" 366 | ] 367 | }, 368 | { 369 | "cell_type": "code", 370 | "execution_count": 12, 371 | "metadata": {}, 372 | "outputs": [ 373 | { 374 | "name": "stdout", 375 | "output_type": "stream", 376 | "text": [ 377 | "SELECT * FROM movie WHERE story LIKE '%뉴욕%' AND story LIKE '%암살%' ;\n", 378 | "{'id': '41', 'story': \"고도의 훈련을 받은 최고의 암살요원 제이슨 본. 사고로 잃었던 기억을 단편적으로 되살리던 제이슨 본은 자신을 암살자로 만든 이들을 찾던 중 ‘블랙브라이어’라는 존재를 알게 된다. ‘블랙브라이어’는 비밀요원을 양성해내던 '트레드스톤'이 국방부 산하의 극비조직으로 재편되면서 더욱 막강한 파워를 가지게 된 비밀기관. 그들에게 자신들의 비밀병기 1호이자 진실을 알고 있는 유일한 인물인 제이슨 본은 반드시 제거해야 하는 대상이다. 니키의 도움으로 블랙브라이어의 실체를 알게 된 제이슨 본은 런던, 마드리드, 모로코 그리고 뉴욕까지 전세계를 실시간 통제하며 자신을 제거하고 비밀을 은폐하려는 조직과 숨막히는 대결을 시작하는데…\", 'title': '본 얼티메이텀', 'link': 'https://movie.naver.com/movie/bi/mi/basic.nhn?code=59075', 'image': 'https://movie-phinf.pstatic.net/20160628_2/1467080136011unJpU_JPEG/movie_image.jpg?type=f67'}\n" 379 | ] 380 | } 381 | ], 382 | "source": [ 383 | "\n", 384 | "\n", 385 | "keyword = \"뉴욕 암살\"\n", 386 | "sql = \"SELECT * FROM movie WHERE \"\n", 387 | "operator = \"AND\"\n", 388 | "for word in keyword.split():\n", 389 | " sql += \"story LIKE '%{}%' {} \".format(word, operator)\n", 390 | "\n", 391 | "sql = sql[:sql.rfind(operator)]+\";\"\n", 392 | "print(sql)\n", 393 | "\n", 394 | "mycursor = mydb.cursor(dictionary=True)\n", 395 | "mycursor.execute(sql)\n", 396 | "\n", 397 | "\n", 398 | "myresult = mycursor.fetchall()\n", 399 | "for x in myresult:\n", 400 | " print(x)" 401 | ] 402 | }, 403 | { 404 | "cell_type": "markdown", 405 | "metadata": {}, 406 | "source": [ 407 | "## 5. Connection 끊어주기" 408 | ] 409 | }, 410 | { 411 | "cell_type": "code", 412 | "execution_count": 13, 413 | "metadata": {}, 414 | "outputs": [], 415 | "source": [ 416 | "mycursor.close()\n", 417 | "mydb.close()" 418 | ] 419 | }, 420 | { 421 | "cell_type": "code", 422 | "execution_count": null, 423 | "metadata": {}, 424 | "outputs": [], 425 | "source": [] 426 | } 427 | ], 428 | "metadata": { 429 | "kernelspec": { 430 | "display_name": "Python 3 (ipykernel)", 431 | "language": "python", 432 | "name": "python3" 433 | }, 434 | "language_info": { 435 | "codemirror_mode": { 436 | "name": "ipython", 437 | "version": 3 438 | }, 439 | "file_extension": ".py", 440 | "mimetype": "text/x-python", 441 | "name": "python", 442 | "nbconvert_exporter": "python", 443 | "pygments_lexer": "ipython3", 444 | "version": "3.8.5" 445 | } 446 | }, 447 | "nbformat": 4, 448 | "nbformat_minor": 4 449 | } 450 | -------------------------------------------------------------------------------- /03_search_engine.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Simple Search Engine\n", 8 | "Elasticsearch와 mysql을 결합한 검색엔진을 만들어보겠습니다.\n", 9 | "____" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "import pandas as pd\n", 19 | "df = pd.read_csv(\"movie_doc.csv\",sep=\"\\t\")" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": 2, 25 | "metadata": {}, 26 | "outputs": [ 27 | { 28 | "data": { 29 | "text/html": [ 30 | "
\n", 31 | "\n", 44 | "\n", 45 | " \n", 46 | " \n", 47 | " \n", 48 | " \n", 49 | " \n", 50 | " \n", 51 | " \n", 52 | " \n", 53 | " \n", 54 | " \n", 55 | " \n", 56 | " \n", 57 | " \n", 58 | " \n", 59 | " \n", 60 | " \n", 61 | " \n", 62 | " \n", 63 | " \n", 64 | " \n", 65 | " \n", 66 | " \n", 67 | " \n", 68 | " \n", 69 | " \n", 70 | " \n", 71 | " \n", 72 | " \n", 73 | " \n", 74 | " \n", 75 | " \n", 76 | " \n", 77 | " \n", 78 | " \n", 79 | " \n", 80 | " \n", 81 | " \n", 82 | " \n", 83 | " \n", 84 | " \n", 85 | " \n", 86 | " \n", 87 | " \n", 88 | " \n", 89 | " \n", 90 | " \n", 91 | " \n", 92 | " \n", 93 | " \n", 94 | " \n", 95 | " \n", 96 | " \n", 97 | " \n", 98 | " \n", 99 | " \n", 100 | " \n", 101 | " \n", 102 | " \n", 103 | "
idtitlestorygenderlinkimage
00인비저블 게스트의문의 습격으로 살해 당한 ‘로라’ ‘아드리안’은 연인의 죽음에 절망하고, 범인은 ...Nhttps://movie.naver.com/movie/bi/mi/basic.nhn?...https://movie-phinf.pstatic.net/20170828_179/1...
11나, 다니엘 블레이크평생을 성실하게 목수로 살아가던 다니엘은 지병인 심장병이 악화되어 일을 계속 해나갈...Fhttps://movie.naver.com/movie/bi/mi/basic.nhn?...https://movie-phinf.pstatic.net/20161117_246/1...
22국가부도의 날1997년, 대한민국 최고의 경제 호황을 믿어 의심치 않았던 그때, 곧 엄청난 경제...Mhttps://movie.naver.com/movie/bi/mi/basic.nhn?...https://movie-phinf.pstatic.net/20181105_232/1...
33당갈전직 레슬링 선수였던 ‘마하비르 싱 포갓(아미르 칸)’은 아버지의 반대로 금메달의 ...Fhttps://movie.naver.com/movie/bi/mi/basic.nhn?...https://movie-phinf.pstatic.net/20180329_2/152...
44스파이더맨: 파 프롬 홈‘엔드게임’ 이후 변화된 세상, 스파이더맨 ‘피터 파커’는 학교 친구들과 유럽 여행...Mhttps://movie.naver.com/movie/bi/mi/basic.nhn?...https://movie-phinf.pstatic.net/20190527_181/1...
\n", 104 | "
" 105 | ], 106 | "text/plain": [ 107 | " id title story \\\n", 108 | "0 0 인비저블 게스트 의문의 습격으로 살해 당한 ‘로라’ ‘아드리안’은 연인의 죽음에 절망하고, 범인은 ... \n", 109 | "1 1 나, 다니엘 블레이크 평생을 성실하게 목수로 살아가던 다니엘은 지병인 심장병이 악화되어 일을 계속 해나갈... \n", 110 | "2 2 국가부도의 날 1997년, 대한민국 최고의 경제 호황을 믿어 의심치 않았던 그때, 곧 엄청난 경제... \n", 111 | "3 3 당갈 전직 레슬링 선수였던 ‘마하비르 싱 포갓(아미르 칸)’은 아버지의 반대로 금메달의 ... \n", 112 | "4 4 스파이더맨: 파 프롬 홈 ‘엔드게임’ 이후 변화된 세상, 스파이더맨 ‘피터 파커’는 학교 친구들과 유럽 여행... \n", 113 | "\n", 114 | " gender link \\\n", 115 | "0 N https://movie.naver.com/movie/bi/mi/basic.nhn?... \n", 116 | "1 F https://movie.naver.com/movie/bi/mi/basic.nhn?... \n", 117 | "2 M https://movie.naver.com/movie/bi/mi/basic.nhn?... \n", 118 | "3 F https://movie.naver.com/movie/bi/mi/basic.nhn?... \n", 119 | "4 M https://movie.naver.com/movie/bi/mi/basic.nhn?... \n", 120 | "\n", 121 | " image \n", 122 | "0 https://movie-phinf.pstatic.net/20170828_179/1... \n", 123 | "1 https://movie-phinf.pstatic.net/20161117_246/1... \n", 124 | "2 https://movie-phinf.pstatic.net/20181105_232/1... \n", 125 | "3 https://movie-phinf.pstatic.net/20180329_2/152... \n", 126 | "4 https://movie-phinf.pstatic.net/20190527_181/1... " 127 | ] 128 | }, 129 | "execution_count": 2, 130 | "metadata": {}, 131 | "output_type": "execute_result" 132 | } 133 | ], 134 | "source": [ 135 | "df.head()" 136 | ] 137 | }, 138 | { 139 | "cell_type": "markdown", 140 | "metadata": {}, 141 | "source": [ 142 | "## 2. MySQL에 데이터 적재" 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": 3, 148 | "metadata": {}, 149 | "outputs": [], 150 | "source": [ 151 | "import mysql.connector\n", 152 | "\n", 153 | "\n", 154 | "HOST = \"localhost\"\n", 155 | "USER = \"root\"\n", 156 | "PASSWORD = \"Boost111@\"\n", 157 | "mydb = mysql.connector.connect(\n", 158 | " host=HOST,\n", 159 | " user=USER,\n", 160 | " password=PASSWORD,\n", 161 | " database=\"movie_db\"\n", 162 | ")\n", 163 | "\n", 164 | "mycursor = mydb.cursor()\n" 165 | ] 166 | }, 167 | { 168 | "cell_type": "code", 169 | "execution_count": 4, 170 | "metadata": {}, 171 | "outputs": [ 172 | { 173 | "name": "stdout", 174 | "output_type": "stream", 175 | "text": [ 176 | "{'id': '0', 'story': '의문의 습격으로 살해 당한 ‘로라’ ‘아드리안’은 연인의 죽음에 절망하고, 범인은 흔적도 없이 사라졌다. 유력한 용의자로 누명을 쓴 ‘아드리안’은 승률 100%의 변호사 ‘버지니아’를 선임한다. 그리고 자신의 무죄를 입증하기 위해 고군분투하던 중 과거 그와 ‘로라’가 은폐한 교통사고와 숨겨진 연관성을 찾게 되는데… 남은 시간은 단 3시간, 사건을 재구성해 무죄를 입증해야 한다!', 'title': '인비저블 게스트', 'link': 'https://movie.naver.com/movie/bi/mi/basic.nhn?code=159516', 'image': 'https://movie-phinf.pstatic.net/20170828_179/1503887362732snsIK_JPEG/movie_image.jpg?type=f67'}\n" 177 | ] 178 | } 179 | ], 180 | "source": [ 181 | "sql = \"SELECT * FROM movie\"\n", 182 | "mycursor = mydb.cursor(dictionary=True)\n", 183 | "mycursor.execute(sql)\n", 184 | "\n", 185 | "\n", 186 | "myresult = mycursor.fetchall()\n", 187 | "for x in myresult:\n", 188 | " print(x)\n", 189 | " break" 190 | ] 191 | }, 192 | { 193 | "cell_type": "markdown", 194 | "metadata": {}, 195 | "source": [ 196 | "## 3. ElasticSearch에 데이터 인덱싱" 197 | ] 198 | }, 199 | { 200 | "cell_type": "code", 201 | "execution_count": 7, 202 | "metadata": {}, 203 | "outputs": [], 204 | "source": [ 205 | "from elasticsearch import Elasticsearch, helpers\n" 206 | ] 207 | }, 208 | { 209 | "cell_type": "code", 210 | "execution_count": 8, 211 | "metadata": {}, 212 | "outputs": [], 213 | "source": [ 214 | "import pprint \n", 215 | "INDEX_NAME = \"movie_index\"\n", 216 | "\n", 217 | "\n", 218 | "INDEX_SETTINGS = {\n", 219 | " \"settings\" : {\n", 220 | " \"index\":{\n", 221 | " \"analysis\":{\n", 222 | " \"analyzer\":{\n", 223 | " \"korean\":{\n", 224 | " \"type\":\"custom\",\n", 225 | " \"tokenizer\":\"nori_tokenizer\",\n", 226 | " \"filter\": [ \"shingle\" ]\n", 227 | " }\n", 228 | " }\n", 229 | " }\n", 230 | " }\n", 231 | " },\n", 232 | " \"mappings\": {\n", 233 | "\n", 234 | " \"properties\" : {\n", 235 | " \"story\" : {\n", 236 | " \"type\" : \"text\",\n", 237 | " \"analyzer\": \"korean\"\n", 238 | " },\n", 239 | " \"title\" : {\n", 240 | " \"type\" : \"text\",\n", 241 | " \"analyzer\": \"korean\"\n", 242 | " }\n", 243 | " }\n", 244 | "\n", 245 | " }\n", 246 | "}\n" 247 | ] 248 | }, 249 | { 250 | "cell_type": "code", 251 | "execution_count": 9, 252 | "metadata": {}, 253 | "outputs": [], 254 | "source": [ 255 | "\n", 256 | "\n", 257 | "try:\n", 258 | " es.transport.close()\n", 259 | "except:\n", 260 | " pass\n", 261 | "es = Elasticsearch()\n", 262 | "\n" 263 | ] 264 | }, 265 | { 266 | "cell_type": "code", 267 | "execution_count": 10, 268 | "metadata": {}, 269 | "outputs": [ 270 | { 271 | "name": "stderr", 272 | "output_type": "stream", 273 | "text": [ 274 | ":1: DeprecationWarning: Using positional arguments for APIs is deprecated and will be disabled in 8.0.0. Instead use only keyword arguments for all APIs. See https://github.com/elastic/elasticsearch-py/issues/1698 for more information\n", 275 | " if es.indices.exists(INDEX_NAME):\n", 276 | "/opt/conda/lib/python3.8/site-packages/elasticsearch/connection/base.py:209: ElasticsearchWarning: Elasticsearch built-in security features are not enabled. Without authentication, your cluster could be accessible to anyone. See https://www.elastic.co/guide/en/elasticsearch/reference/7.15/security-minimal-setup.html to enable security.\n", 277 | " warnings.warn(message, category=ElasticsearchWarning)\n", 278 | ":3: DeprecationWarning: The 'body' parameter is deprecated for the 'create' API and will be removed in a future version. Instead use API parameters directly. See https://github.com/elastic/elasticsearch-py/issues/1698 for more information\n", 279 | " es.indices.create(index=INDEX_NAME, body=INDEX_SETTINGS)\n" 280 | ] 281 | }, 282 | { 283 | "data": { 284 | "text/plain": [ 285 | "{'acknowledged': True, 'shards_acknowledged': True, 'index': 'movie_index'}" 286 | ] 287 | }, 288 | "execution_count": 10, 289 | "metadata": {}, 290 | "output_type": "execute_result" 291 | } 292 | ], 293 | "source": [ 294 | "if es.indices.exists(INDEX_NAME):\n", 295 | " es.indices.delete(index=INDEX_NAME)\n", 296 | "es.indices.create(index=INDEX_NAME, body=INDEX_SETTINGS)" 297 | ] 298 | }, 299 | { 300 | "cell_type": "code", 301 | "execution_count": 11, 302 | "metadata": {}, 303 | "outputs": [ 304 | { 305 | "data": { 306 | "text/plain": [ 307 | "[{'_index': 'movie_index',\n", 308 | " '_id': '0',\n", 309 | " '_source': {'title': '인비저블 게스트',\n", 310 | " 'story': '의문의 습격으로 살해 당한 ‘로라’ ‘아드리안’은 연인의 죽음에 절망하고, 범인은 흔적도 없이 사라졌다. 유력한 용의자로 누명을 쓴 ‘아드리안’은 승률 100%의 변호사 ‘버지니아’를 선임한다. 그리고 자신의 무죄를 입증하기 위해 고군분투하던 중 과거 그와 ‘로라’가 은폐한 교통사고와 숨겨진 연관성을 찾게 되는데… 남은 시간은 단 3시간, 사건을 재구성해 무죄를 입증해야 한다!'}},\n", 311 | " {'_index': 'movie_index',\n", 312 | " '_id': '1',\n", 313 | " '_source': {'title': '나, 다니엘 블레이크',\n", 314 | " 'story': '평생을 성실하게 목수로 살아가던 다니엘은 지병인 심장병이 악화되어 일을 계속 해나갈 수 없는 상황이 된다. 다니엘은 실업급여를 받기 위해 찾아간 관공서에서 복잡하고 관료적인 절차 때문에 번번히 좌절한다. 그러던 어느 날 다니엘은 두 아이와 함께 런던에서 이주한 싱글맘 케이티를 만나 도움을 주게되고, 서로를 의지하게 되는데...'}},\n", 315 | " {'_index': 'movie_index',\n", 316 | " '_id': '2',\n", 317 | " '_source': {'title': '국가부도의 날',\n", 318 | " 'story': '1997년, 대한민국 최고의 경제 호황을 믿어 의심치 않았던 그때, 곧 엄청난 경제 위기가 닥칠 것을 예견한 한국은행 통화정책팀장 ‘한시현’(김혜수)은 이 사실을 보고하고, 정부는 뒤늦게 국가부도 사태를 막기 위한 비공개 대책팀을 꾸린다. 한편, 곳곳에서 감지되는 위기의 시그널을 포착하고 과감히 사표를 던진 금융맨 ‘윤정학’(유아인)은 국가부도의 위기에 투자하는 역베팅을 결심, 투자자들을 모으기 시작한다. 이런 상황을 알 리 없는 작은 공장의 사장이자 평범한 가장 ‘갑수’(허준호)는 대형 백화점과의 어음 거래 계약서에 도장을 찍고 소박한 행복을 꿈꾼다. 국가부도까지 남은 시간 단 일주일. 대책팀 내부에서 위기대응 방식을 두고 시현과 ‘재정국 차관’(조우진)이 강하게 대립하는 가운데, 시현의 반대에도 불구하고 ‘IMF 총재’(뱅상 카셀)가 협상을 위해 비밀리에 입국하는데… 위기를 막으려는 사람과 위기에 베팅하는 사람, 그리고 회사와 가족을 지키려는 평범한 사람, 1997년, 서로 다른 선택을 했던 사람들의 이야기가 시작된다!'}},\n", 319 | " {'_index': 'movie_index',\n", 320 | " '_id': '3',\n", 321 | " '_source': {'title': '당갈',\n", 322 | " 'story': '전직 레슬링 선수였던 ‘마하비르 싱 포갓(아미르 칸)’은 아버지의 반대로 금메달의 꿈을 이루지 못한 채 레슬링을 포기한다. 아들을 통해 꿈을 이루겠다는 생각은 내리 딸만 넷이 태어나면서 좌절된다. 그러던 어느 날, 두 딸이 또래 남자아이들을 신나게 때린 모습에서 잠재력을 발견하고 레슬링 특훈에 돌입한다. 사람들의 따가운 시선과 조롱에도 불구하고 첫째 기타(파티마 사나 셰이크)와 둘째 바비타(산야 말호트라)는 아버지의 훈련 속에 재능을 발휘, 승승장구 승리를 거두며 국가대표 레슬러로까지 성장해 마침내 국제대회에 출전하는데...'}},\n", 323 | " {'_index': 'movie_index',\n", 324 | " '_id': '4',\n", 325 | " '_source': {'title': '스파이더맨: 파 프롬 홈',\n", 326 | " 'story': '‘엔드게임’ 이후 변화된 세상, 스파이더맨 ‘피터 파커’는 학교 친구들과 유럽 여행을 떠나게 된다. 그런 그의 앞에 ‘닉 퓨리’가 등장해 도움을 요청하고 정체불명의 조력자 ‘미스테리오’까지 합류하게 되면서 전 세계를 위협하는 새로운 빌런 ‘엘리멘탈 크리쳐스’와 맞서야만 하는 상황에 놓이게 되는데…'}},\n", 327 | " {'_index': 'movie_index',\n", 328 | " '_id': '5',\n", 329 | " '_source': {'title': '뚜르: 내 생애 최고의 49일',\n", 330 | " 'story': '스물여섯 윤혁은 희귀암 말기 판정을 받지만 운명처럼 찾아온 자전거로 새로운 희망을 꿈꾼다. 생애 최대 좌절의 순간 세계 최고의 자전거 대회 ‘뚜르드프랑스’ 완주를 꿈꾸는 윤혁. 항암치료를 중단하고 그를 위해 모인 9인의 드림팀과 함께 우여곡절 끝에 드디어 프랑스에 입성한다. 하지만 첫 라이딩에서 메카닉은 팔이 부러지고, 팀닥터는 불편한 숙소에 불만이 폭발한다. 현지 코디네이터는 적은 예산과 팀원들의 불신으로 좌괴감에 빠지고, 라이딩 파트너는 체력 고갈로 바람막이는커녕 점점 쉬는 날이 많아진다. 좌충우돌 속에서도 첫번째 고비인 피레네 산맥을 하루만에 넘은 윤혁! 드림팀 멤버들은 다시 심기일전 힘을 모으기로 하는데… 싸우고 화해하고 울고 웃으며 서로의 마음을 하나로 모아가는 드림팀과 윤혁! 한국인 최초 ‘뚜르드프랑스’ 완주의 49일, 3,500km의 뜨거운 도전이 시작된다!'}},\n", 331 | " {'_index': 'movie_index',\n", 332 | " '_id': '6',\n", 333 | " '_source': {'title': '곰돌이 푸 다시 만나 행복해',\n", 334 | " 'story': '“어른이 된 나 인생의 쉼표가 필요한 순간, 찾아온 나의 친구들 다시 만나 행복해” 어른이 된 나 로빈(이완 맥그리거)은 가족도 일도 모두 완벽해 보이지만, 한편 지쳐가는 일상 속에 서있다. 어느 날, 눈 앞에 가장 행복한 시간을 함께했던 비밀 친구 ‘곰돌이 푸와 일행’들이 다시 찾아오게 되고 뜻하지 않게 놀라운 모험 속에 빠져들게 되는데…'}},\n", 335 | " {'_index': 'movie_index',\n", 336 | " '_id': '7',\n", 337 | " '_source': {'title': '연인',\n", 338 | " 'story': '가난한 10대 프랑스 소녀, 부유한 남자를 허락하고 처음으로 육체적 쾌락을 경험하게 된다. 불우한 가정 환경과 자신에 대한 혐오가 더해 갈수록 소녀는 욕망에 빠져들고 격정적인 관능에 몰입한다. 욕정일 뿐 사랑이 아니라고 부정하지만 평생 잊을 수 없는 운명으로 남게 되는데…. 욕망으로 남기에는 아름다운 세기의 로맨스, 마르그리뜨 뒤라스 소설 <연인>이 스크린으로 다시 돌아온다!'}},\n", 339 | " {'_index': 'movie_index',\n", 340 | " '_id': '8',\n", 341 | " '_source': {'title': '짱구는 못말려 극장판: 정면승부! 로봇아빠의 역습',\n", 342 | " 'story': '어느 날, 로봇이 되어 돌아온 짱구 아빠! 짱구는 그 동안 영화에서만 봤던 슈퍼히어로가 되어 특별한 능력을 보여주는 로봇 아빠가 자랑스럽다. 그러나 갑자기 무시무시한 모습으로 변하는 로봇 아빠와 함께 떡잎마을을 무너뜨리려는 거대한 음모가 드러나는데… 과연, 로봇 아빠와 짱구 가족은 마을의 평화를 지켜낼 수 있을까?'}},\n", 343 | " {'_index': 'movie_index',\n", 344 | " '_id': '9',\n", 345 | " '_source': {'title': '그날 본 꽃의 이름을 우리는 아직 모른다',\n", 346 | " 'story': '어린 시절 단짝 친구들인 ‘초평화 버스터즈’ 6인방. 감춰 두었던 첫사랑을 수줍게 고백한 어느 여름날, ‘멘마’가 갑작스레 모두의 곁을 떠난다. 이후 각자 상처와 짐을 안은 채 뿔뿔이 흩어진 친구들. 그리고 5년 후, 팀의 리더였던 ‘진땅’의 앞에 ‘멘마’가 나타난다. 모두와 함께 소원을 이루고 싶다는 그녀. ‘진땅’은 그녀가 진심으로 바라는 소원을 찾기 위해 멀어졌던 친구들을 다시 찾게 된다. 무심코 준 상처, 전하지 못한 진심. 내일 말하면 된다고 생각하다가 모든 것을 놓쳐버린 그들에게 다시 찾아 온 기회. 친구들 사이에 멈춰있던 시간이 다시 흐르기 시작한다. 과연 이들은 ‘멘마’의 소원을 이루고 한여름의 첫사랑을 되돌릴 수 있을까?'}}]" 347 | ] 348 | }, 349 | "execution_count": 11, 350 | "metadata": {}, 351 | "output_type": "execute_result" 352 | } 353 | ], 354 | "source": [ 355 | "movies = [\n", 356 | " {\n", 357 | " \"_index\": INDEX_NAME,\n", 358 | " \"_id\" : doc['id'],\n", 359 | " \"_source\": {\n", 360 | " \"title\": doc['title'],\n", 361 | " \"story\": doc['story'],\n", 362 | " }\n", 363 | " }\n", 364 | " for doc in myresult\n", 365 | "]\n", 366 | "\n", 367 | "movies[:10]" 368 | ] 369 | }, 370 | { 371 | "cell_type": "code", 372 | "execution_count": 12, 373 | "metadata": {}, 374 | "outputs": [ 375 | { 376 | "name": "stdout", 377 | "output_type": "stream", 378 | "text": [ 379 | "\n", 380 | "RESPONSE: (403, [])\n" 381 | ] 382 | } 383 | ], 384 | "source": [ 385 | "try:\n", 386 | " response = helpers.bulk(es, movies)\n", 387 | " print (\"\\nRESPONSE:\", response)\n", 388 | "except Exception as e:\n", 389 | " print(\"\\nERROR:\", e)" 390 | ] 391 | }, 392 | { 393 | "cell_type": "code", 394 | "execution_count": 13, 395 | "metadata": {}, 396 | "outputs": [ 397 | { 398 | "name": "stdout", 399 | "output_type": "stream", 400 | "text": [ 401 | "{'_id': '1',\n", 402 | " '_index': 'movie_index',\n", 403 | " '_primary_term': 1,\n", 404 | " '_seq_no': 1,\n", 405 | " '_source': {'story': '평생을 성실하게 목수로 살아가던 다니엘은 지병인 심장병이 악화되어 일을 계속 해나갈 수 없는 상황이 '\n", 406 | " '된다. 다니엘은 실업급여를 받기 위해 찾아간 관공서에서 복잡하고 관료적인 절차 때문에 번번히 '\n", 407 | " '좌절한다. 그러던 어느 날 다니엘은 두 아이와 함께 런던에서 이주한 싱글맘 케이티를 만나 도움을 '\n", 408 | " '주게되고, 서로를 의지하게 되는데...',\n", 409 | " 'title': '나, 다니엘 블레이크'},\n", 410 | " '_type': '_doc',\n", 411 | " '_version': 1,\n", 412 | " 'found': True}\n" 413 | ] 414 | } 415 | ], 416 | "source": [ 417 | "doc = es.get(index=INDEX_NAME, id=1)\n", 418 | "pprint.pprint(doc)" 419 | ] 420 | }, 421 | { 422 | "cell_type": "markdown", 423 | "metadata": {}, 424 | "source": [ 425 | "## 4. ElasticSearch에서 검색후, 검색결과를 MySQL에서 재 검색" 426 | ] 427 | }, 428 | { 429 | "cell_type": "code", 430 | "execution_count": 14, 431 | "metadata": {}, 432 | "outputs": [ 433 | { 434 | "name": "stdout", 435 | "output_type": "stream", 436 | "text": [ 437 | "Doc ID: '67' Score: 12.94\n", 438 | "Title: 존 윅 - 리로드\n", 439 | "줄거리: 업계 최고의 레전드 킬러 ‘존 윅’은 과거를 뒤로한 채 은퇴를 선언하지만, 과거 자신의 목숨을 구했던 옛 동료와 피로 맺은 암살자들의 룰에 의해 로마로 향한다. ‘국제 암살자 연합’을 탈취하려는 옛 동료의 계획으로 ‘존 윅’은 함정에 빠지게 되고, 전세계 암살자들의 총구는 그를 향하는데...\n", 440 | "Doc ID: '301' Score: 7.92\n", 441 | "Title: 블레이드 러너\n", 442 | "줄거리: 핵전쟁 이후 혼돈으로 무질서로 휩싸인 2019년, 복제인간 ‘로이’를 포함한 ‘넥서스 6’이 오프월드에서 반란을 일으킨 후 지구로 잠입한다. 은퇴한 블레이드 러너였던 '데커드'(해리슨 포드)는 지구에 잠입한 복제 인간들을 찾는 임무와 함께 강제로 복직하게 되고, 탐문 수사를 위해 찾아간 넥서스 6 제조사인 타이렐 사에서 자신이 복제 인간임을 모르는 ‘레이첼’(숀 영)을 마주하게 된다. 한편, 증거의 꼬리를 잡아 수사하던 도중 ‘데커드’는 ‘레이첼’ 덕분에 위기 속에서 목숨을 구원받게 되고, 복제 인간과의 마지막 전투를 앞두게 된다.\n", 443 | "Doc ID: '181' Score: 7.65\n", 444 | "Title: 수상한 그녀\n", 445 | "줄거리: 아들 자랑이 유일한 낙인 욕쟁이 칠순 할매 오말순(나문희分)은 어느 날, 가족들이 자신을 요양원으로 독립(?)시키려 한다는 청천벽력 같은 사실을 알게 된다. 뒤숭숭한 마음을 안고 밤길을 방황하던 할매 말순은 오묘한 불빛에 이끌려 ‘청춘 사진관’으로 들어간다. 난생 처음 곱게 꽃단장을 하고 영정사진을 찍고 나오는 길, 그녀는 버스 차창 밖에 비친 자신의 얼굴을 보고 경악을 금치 못한다. 오드리 헵번처럼 뽀얀 피부, 날렵한 몸매... 주름진 할매에서 탱탱한 꽃처녀의 몸으로 돌아간 것! 아무도 알아보지 못하는 자신의 젊은 모습에 그녀는 스무살 ‘오두리’가 되어 빛나는 전성기를 즐겨 보기로 마음 먹는데... 2014년 새해, 대한민국에 웃음 보따리를 안겨줄 <수상한 그녀>가 온다!\n", 446 | "Doc ID: '201' Score: 7.65\n", 447 | "Title: 달콤한 인생\n", 448 | "줄거리: 어느 맑은 봄날, 바람에 이리저리 휘날리는 나뭇가지를 바라보며, 제자가 물었다. “스승님, 저것은 나뭇가지가 움직이는 겁니까, 바람이 움직이는 겁니까?” 스승은 제자가 가리키는 것은 보지도 않은 채, 웃으며 말했다. 무릇 움직이는 것은 나뭇가지도 아니고 바람도 아니며, 네 마음 뿐이다. 서울 하늘 한 켠, 섬처럼 떠 있는 한 호텔의 스카이라운지. 그 곳은 냉철하고 명민한 완벽주의자 선우의 작은 성이다. '왜'라고 묻지 않는 과묵한 의리, 빈틈 없는 일 처리로 보스 강사장의 절대적 신뢰를 획득, 스카이라운지의 경영을 책임지기까지, 그는 꼬박 7년의 세월을 바쳤다. 룰을 어긴 자는 이유를 막론하고 처단하는 냉혹한 보스 강사장. 그런 그에게는 남들에게 말 못 할 비밀이 하나 있다. 젊은 애인 희수의 존재가 바로 그것. 그녀에게 딴 남자가 생긴 것 같다는 의혹을 가진 강사장은 선우에게 그녀를 감시, 사실이면 처리하라고 명령한다. 희수를 따라 다니기 시작한 지 3일째, 희수와 남자 친구가 함께 있는 현장을 급습하는 선우. 하지만, 마지막 순간, 그는 알 수 없는 망설임 끝에 그들을 놓아준다. 그것이 모두를 위한 최선의 선택이라 믿으며 말이다. 그러나 단 한 순간에 불과했던 이 선택으로 인해 선우는 어느 새 적이 되어 버린 조직 전체를 상대로, 돌이킬 수 없는 전쟁을 시작하게 되는데... 어느 깊은 가을밤, 잠에서 깨어난 제자가 울고 있었다. 그 모습을 본 스승이 기이하게 여겨 제자에게 물었다. “무서운 꿈을 꾸었느냐?” “아닙니다.” “슬픈 꿈을 꾸었느냐?” “아닙니다. 달콤한 꿈을 꾸었습니다.” “그런데 왜 그리 슬피 우느냐?” 제자는 흐르는 눈물을 닦아내며 나지막히 말했다. “그 꿈은 이루어질 수 없기 때문입니다.” \n", 449 | "Doc ID: '54' Score: 7.62\n", 450 | "Title: 헌터 킬러\n", 451 | "줄거리: 미 국방부는 격침당한 잠수함의 행방을 찾기 위해 ‘헌터 킬러’를 극비리에 투입시키고 캡틴 ‘글래스’(제라드 버틀러)는 배후에 숨겨진 음모가 있음을 알게 된다. 한편, 지상에서는 VIP가 납치되어 전세계는 초긴장 상태에 놓이게 되는데… 일촉즉발 위기상황, VIP를 구출하라! 단 한 척의 공격 잠수함 ‘헌터 킬러’와 최정예 특수부대 네이비 씰의 숨막히는 육해공 합동 작전이 펼쳐진다!\n", 452 | "Doc ID: '179' Score: 7.33\n", 453 | "Title: 사우스포\n", 454 | "줄거리: 43승 0패의 무패 신화를 달리고 있는 라이트 헤비급 복싱 세계챔피언 ‘빌리 호프’(제이크 질렌할). 사랑하는 가족과 함께 호화로운 삶을 누리던 그는 어느 날 한 순간의 실수로 아름다운 아내 '모린’(레이첼 맥아덤즈)을 잃고 만다. 예상치 못한 비극에 믿었던 매니저와 친구들마저 떠나버리고, 자책과 절망 속에 살아가던 그는 결국 하나뿐인 딸 ‘레일라’의 양육권마저 빼앗길 위기에 처하게 된다. 이제 남은 것이라곤 두 주먹뿐인 그가 찾아간 곳은 다 무너져가는 동네 체육관에서 아마추어 복서들을 가르치는 은퇴한 복싱 선수 ‘틱’(포레스트 휘태커). ‘틱’은 분노로 가득찬 빌리에게 스스로를 보호하는 싸움법과 왼손잡이 펀치, ‘사우스포’를 가르친다. 이제 빌리는 딸을 되찾고 자랑스러운 아빠로 거듭나기 위해 생애 가장 어려운 시합에 올라서기로 결심하는데… 마침내 시작된 최후의 도전! 멈춰버린 심장을 다시 뛰게 할 짜릿한 승부가 펼쳐진다!\n", 455 | "Doc ID: '249' Score: 7.18\n", 456 | "Title: 위대한 독재자\n", 457 | "줄거리: 세계대전에서 패배한 토매니아국에 힌켈이라는 독재자가 나타나 악명을 떨친다. 한편, 힌켈과 닮은꼴 외모의 이발사 찰리는 국가의 유태인 탄압정책으로 인해 곤경에 처하지만 병사로 참전했던 전쟁에서 우연히 구해줬던 슐츠 장교의 도움을 받아 위기를 모면한다. 독재자 힌켈의 악행은 갈수록 도를 더해가고, 찰리는 유태인 수용소에 끌려가게 되지만 기지를 부려 탈옥에 성공한다. 하지만 이발사와 똑같은 얼굴을 한 힌켈이 탈옥범으로 오해 받아 감옥에 잡혀 들어가게 되는데…\n", 458 | "Doc ID: '42' Score: 6.90\n", 459 | "Title: 레옹\n", 460 | "줄거리: 한 손엔 우유 2팩이 든 가방, 다른 한 손엔 화분을 들고 뿌리 없이 떠도는 킬러 레옹은 어느 날 옆집 소녀 마틸다의 일가족이 몰살 당하는 것을 목격한다. 그 사이 심부름을 갔다 돌아 온 마틸다는 가족들이 처참히 몰살 당하자 레옹에게 도움을 청한다. 가족의 원수를 갚기 위해 킬러가 되기로 결심한 12세 소녀 마틸다는 레옹에게 글을 알려주는 대신 복수하는 법을 배우게 된다. 드디어 그녀는 가족을 죽인 사람이 부패 마약 경찰 스탠스임을 알게 되고, 그의 숙소로 향하게 되는데…\n", 461 | "Doc ID: '88' Score: 6.77\n", 462 | "Title: 성실한 나라의 앨리스\n", 463 | "줄거리: 제가 이래봬도 스펙이 좋거든요. 제 자랑은 아니지만 자격증이 한 14개? 어렸을 때부터 손으로 하는건 뭐든지 잘했어요~ 근데 결국 컴퓨터에 일자리를 뺏겼죠. 그래도 다행이 취직도 하고, 사랑하는 남편까지 만났어요. 그래서 둘이 함께 살 집을 사기로 결심했죠. 잠도 줄여가며 투잡 쓰리잡 열심히 일했어요. 근데 아무리 꾸준히 일해도 빚은 더 쌓이더라고요. 그러다 빚을 한방에 청산할 기회가 찾아왔는데! 왜 행복을 방해하는 사람들이 자꾸 생기는 걸까요? 이제 제 손재주를 다르게 써보려고요. 더 이상 당하고만 있지 않을 거예요! 5포세대에 고함! 열심히 살아도 행복해 질 수 없는 세상, 그녀의 통쾌한 복수가 시작된다!\n", 464 | "Doc ID: '244' Score: 6.77\n", 465 | "Title: 나의 특별한 형제\n", 466 | "줄거리: 비상한 두뇌를 가졌지만 동생 ‘동구’ 없이는 아무 데도 못 가는 형 ‘세하’(신하균), 뛰어난 수영실력을 갖췄지만 형 ‘세하’ 없이는 아무것도 못 하는 동생 ‘동구’(이광수). 이들은 피 한 방울 섞이지 않았지만 20년 동안 한 몸처럼 살아온 ‘특별한 형제’다. 어느 날 형제의 보금자리 ‘책임의 집’을 운영하던 신부님이 돌아가시자 모든 지원금이 끊기게 되고, 각각 다른 장애를 가진 두 사람은 헤어질 위기에 처하고 만다. 세하는 ‘책임의 집’을 지키고 동구와 떨어지지 않기 위해 구청 수영장 알바생이자 취준생 ‘미현’(이솜)을 수영코치로 영입하고, 동구를 수영대회에 출전시켜 사람들의 이목을 집중시키는 데 성공한다. 헤어지지 않을 수 있다는 희망을 본 것도 잠시, 예상치 못한 인물이 형제 앞에 등장하면서 형제는 새로운 위기를 겪게 되는데...!\n" 467 | ] 468 | } 469 | ], 470 | "source": [ 471 | "query=\"은퇴한 킬러\"\n", 472 | "res = es.search(index=INDEX_NAME, q=query)\n", 473 | "for hit in res['hits']['hits']:\n", 474 | " print(\"Doc ID: %3r Score: %5.2f\" % (hit['_id'], hit['_score']))\n", 475 | " print(\"Title: {}\".format(hit['_source']['title']))\n", 476 | " print(\"줄거리: {}\".format(hit['_source']['story']))\n" 477 | ] 478 | }, 479 | { 480 | "cell_type": "code", 481 | "execution_count": 15, 482 | "metadata": {}, 483 | "outputs": [], 484 | "source": [ 485 | "movie_id = res['hits']['hits'][0][\"_id\"]" 486 | ] 487 | }, 488 | { 489 | "cell_type": "code", 490 | "execution_count": 16, 491 | "metadata": {}, 492 | "outputs": [ 493 | { 494 | "name": "stdout", 495 | "output_type": "stream", 496 | "text": [ 497 | "{'id': '67', 'story': '업계 최고의 레전드 킬러 ‘존 윅’은 과거를 뒤로한 채 은퇴를 선언하지만, 과거 자신의 목숨을 구했던 옛 동료와 피로 맺은 암살자들의 룰에 의해 로마로 향한다. ‘국제 암살자 연합’을 탈취하려는 옛 동료의 계획으로 ‘존 윅’은 함정에 빠지게 되고, 전세계 암살자들의 총구는 그를 향하는데...', 'title': '존 윅 - 리로드', 'link': 'https://movie.naver.com/movie/bi/mi/basic.nhn?code=143932', 'image': 'https://movie-phinf.pstatic.net/20200807_193/1596789703698oLgpX_JPEG/movie_image.jpg?type=f67'}\n" 498 | ] 499 | } 500 | ], 501 | "source": [ 502 | "sql = \"SELECT * FROM movie WHERE id = {}\".format(movie_id)\n", 503 | "mycursor = mydb.cursor(dictionary=True)\n", 504 | "mycursor.execute(sql)\n", 505 | "\n", 506 | "\n", 507 | "myresult = mycursor.fetchall()\n", 508 | "for x in myresult:\n", 509 | " print(x)" 510 | ] 511 | }, 512 | { 513 | "cell_type": "code", 514 | "execution_count": null, 515 | "metadata": {}, 516 | "outputs": [], 517 | "source": [ 518 | "#쿼리---> 엘라스틱서치 --> MySQL" 519 | ] 520 | } 521 | ], 522 | "metadata": { 523 | "kernelspec": { 524 | "display_name": "Python 3 (ipykernel)", 525 | "language": "python", 526 | "name": "python3" 527 | }, 528 | "language_info": { 529 | "codemirror_mode": { 530 | "name": "ipython", 531 | "version": 3 532 | }, 533 | "file_extension": ".py", 534 | "mimetype": "text/x-python", 535 | "name": "python", 536 | "nbconvert_exporter": "python", 537 | "pygments_lexer": "ipython3", 538 | "version": "3.8.5" 539 | } 540 | }, 541 | "nbformat": 4, 542 | "nbformat_minor": 4 543 | } 544 | -------------------------------------------------------------------------------- /02_elastic_search.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Elasticsearch\n", 8 | "엘라스틱서치의 기본 원리와 사용법에 대해서 배웁니다.\n", 9 | "____" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "## 1. Elasticsearch의 Python 라이브러리 설치" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": 1, 22 | "metadata": {}, 23 | "outputs": [ 24 | { 25 | "name": "stdout", 26 | "output_type": "stream", 27 | "text": [ 28 | "Collecting elasticsearch\n", 29 | " Downloading elasticsearch-7.15.1-py2.py3-none-any.whl (378 kB)\n", 30 | "\u001b[K |████████████████████████████████| 378 kB 21.4 MB/s eta 0:00:01\n", 31 | "\u001b[?25hRequirement already satisfied: certifi in /opt/conda/lib/python3.8/site-packages (from elasticsearch) (2020.12.5)\n", 32 | "Requirement already satisfied: urllib3<2,>=1.21.1 in /opt/conda/lib/python3.8/site-packages (from elasticsearch) (1.25.11)\n", 33 | "Installing collected packages: elasticsearch\n", 34 | "Successfully installed elasticsearch-7.15.1\n" 35 | ] 36 | } 37 | ], 38 | "source": [ 39 | "!pip install elasticsearch" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": 2, 45 | "metadata": {}, 46 | "outputs": [], 47 | "source": [ 48 | "from elasticsearch import Elasticsearch" 49 | ] 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "metadata": {}, 54 | "source": [ 55 | "## 2. Index 세팅\n", 56 | "- 한글 데이터를 다루기 때문에, 노리 형태소 분석기를 탑재합니다." 57 | ] 58 | }, 59 | { 60 | "cell_type": "code", 61 | "execution_count": 35, 62 | "metadata": {}, 63 | "outputs": [], 64 | "source": [ 65 | "import pprint \n", 66 | "INDEX_NAME = \"toy_index\"\n", 67 | "\n", 68 | "\n", 69 | "INDEX_SETTINGS = {\n", 70 | " \"settings\" : {\n", 71 | " \"index\":{\n", 72 | " \"analysis\":{\n", 73 | " \"analyzer\":{\n", 74 | " \"korean\":{\n", 75 | " \"type\":\"custom\",\n", 76 | " \"tokenizer\":\"nori_tokenizer\",\n", 77 | " \"filter\": [ \"shingle\" ],\n", 78 | "\n", 79 | " }\n", 80 | " }\n", 81 | " }\n", 82 | " }\n", 83 | " },\n", 84 | " \"mappings\": {\n", 85 | "\n", 86 | " \"properties\" : {\n", 87 | " \"content\" : {\n", 88 | " \"type\" : \"text\",\n", 89 | " \"analyzer\": \"korean\",\n", 90 | " \"search_analyzer\": \"korean\"\n", 91 | " },\n", 92 | " \"title\" : {\n", 93 | " \"type\" : \"text\",\n", 94 | " \"analyzer\": \"korean\",\n", 95 | " \"search_analyzer\": \"korean\"\n", 96 | " }\n", 97 | " }\n", 98 | "\n", 99 | " }\n", 100 | "}\n" 101 | ] 102 | }, 103 | { 104 | "cell_type": "markdown", 105 | "metadata": {}, 106 | "source": [ 107 | "## 3. 문서 준비" 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "execution_count": 36, 113 | "metadata": {}, 114 | "outputs": [], 115 | "source": [ 116 | "DOCS = {\n", 117 | " 1: {\"title\": \"My Love\",\n", 118 | " \"content\": \"사랑해 그 말은 무엇보다 아픈 말 숨죽여서 하는 말 이젠 하기 힘든 말\"\n", 119 | " },\n", 120 | " 2: {\"title\": \"듣고있나요\",\n", 121 | " \"content\": \"끝내 우린 스쳐가나요 기억넘어 서로를 지워야하나요 내게 사랑이 준 깊은 상처는 어떻게 견디며 살아야하는지 매일 아픈 그리움속에 가슴 텅 빈채 살아도 그대를 사랑했던일 그것만은 죽어도 나 후회하지않아요\"\n", 122 | " },\n", 123 | " 3: {\"title\": \"인연\",\n", 124 | " \"content\": \"눈을 떠 바라보아요 그댄 정말 가셨나요 단 한번 보내준 그대 눈빛은 날 사랑했나요 또 다른 사랑이 와도 이젠 쉽게 허락되진 않아 견디기 힘들어 운명같은 우연을\"\n", 125 | " },\n", 126 | " 4: {\"title\": \"말리 꽃\",\n", 127 | " \"content\": [\"얼마나 더 견뎌야 하는지 짙은 어둠을 헤메고 있어\",\"내가 바란 꿈이라는 것은 없는걸까\", \"더 이상은 견딜수 없는 것\"]\n", 128 | " },\n", 129 | " 5: {\"title\": \"그런 사람 또 없습니다\",\n", 130 | " \"content\": \"천번 번이고 다시 태어난대도 그런 사람 또 없을테죠 슬픈 내삶을 따뜻하게 해준 잠 고마운 사람입니다\"\n", 131 | " }\n", 132 | "} " 133 | ] 134 | }, 135 | { 136 | "cell_type": "markdown", 137 | "metadata": {}, 138 | "source": [ 139 | "## 4. Elastichsearch 접속" 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": 37, 145 | "metadata": {}, 146 | "outputs": [], 147 | "source": [ 148 | "try:\n", 149 | " es.transport.close()\n", 150 | "except:\n", 151 | " pass\n", 152 | "es = Elasticsearch()\n", 153 | "\n" 154 | ] 155 | }, 156 | { 157 | "cell_type": "code", 158 | "execution_count": 38, 159 | "metadata": {}, 160 | "outputs": [ 161 | { 162 | "data": { 163 | "text/plain": [ 164 | "{'name': 'c809fa30e588',\n", 165 | " 'cluster_name': 'elasticsearch',\n", 166 | " 'cluster_uuid': 'oljvzM9tSzOPJMQGQyxZBA',\n", 167 | " 'version': {'number': '7.15.1',\n", 168 | " 'build_flavor': 'default',\n", 169 | " 'build_type': 'deb',\n", 170 | " 'build_hash': '83c34f456ae29d60e94d886e455e6a3409bba9ed',\n", 171 | " 'build_date': '2021-10-07T21:56:19.031608185Z',\n", 172 | " 'build_snapshot': False,\n", 173 | " 'lucene_version': '8.9.0',\n", 174 | " 'minimum_wire_compatibility_version': '6.8.0',\n", 175 | " 'minimum_index_compatibility_version': '6.0.0-beta1'},\n", 176 | " 'tagline': 'You Know, for Search'}" 177 | ] 178 | }, 179 | "execution_count": 38, 180 | "metadata": {}, 181 | "output_type": "execute_result" 182 | } 183 | ], 184 | "source": [ 185 | "es.info()" 186 | ] 187 | }, 188 | { 189 | "cell_type": "markdown", 190 | "metadata": {}, 191 | "source": [ 192 | "## 5. 인덱스 생성" 193 | ] 194 | }, 195 | { 196 | "cell_type": "code", 197 | "execution_count": 39, 198 | "metadata": { 199 | "scrolled": true 200 | }, 201 | "outputs": [ 202 | { 203 | "name": "stderr", 204 | "output_type": "stream", 205 | "text": [ 206 | ":1: DeprecationWarning: Using positional arguments for APIs is deprecated and will be disabled in 8.0.0. Instead use only keyword arguments for all APIs. See https://github.com/elastic/elasticsearch-py/issues/1698 for more information\n", 207 | " if es.indices.exists(INDEX_NAME):\n", 208 | ":3: DeprecationWarning: The 'body' parameter is deprecated for the 'create' API and will be removed in a future version. Instead use API parameters directly. See https://github.com/elastic/elasticsearch-py/issues/1698 for more information\n", 209 | " es.indices.create(index=INDEX_NAME, body=INDEX_SETTINGS)\n" 210 | ] 211 | }, 212 | { 213 | "data": { 214 | "text/plain": [ 215 | "{'acknowledged': True, 'shards_acknowledged': True, 'index': 'toy_index'}" 216 | ] 217 | }, 218 | "execution_count": 39, 219 | "metadata": {}, 220 | "output_type": "execute_result" 221 | } 222 | ], 223 | "source": [ 224 | "if es.indices.exists(INDEX_NAME):\n", 225 | " es.indices.delete(index=INDEX_NAME)\n", 226 | "es.indices.create(index=INDEX_NAME, body=INDEX_SETTINGS)" 227 | ] 228 | }, 229 | { 230 | "cell_type": "markdown", 231 | "metadata": {}, 232 | "source": [ 233 | "## 6. 문서삽입" 234 | ] 235 | }, 236 | { 237 | "cell_type": "code", 238 | "execution_count": 40, 239 | "metadata": {}, 240 | "outputs": [ 241 | { 242 | "name": "stderr", 243 | "output_type": "stream", 244 | "text": [ 245 | ":3: DeprecationWarning: The 'body' parameter is deprecated for the 'index' API and will be removed in a future version. Instead use the 'document' parameter. See https://github.com/elastic/elasticsearch-py/issues/1698 for more information\n", 246 | " es.index(index=INDEX_NAME, id=doc_id, body=doc)\n" 247 | ] 248 | } 249 | ], 250 | "source": [ 251 | "import time\n", 252 | "for doc_id, doc in DOCS.items():\n", 253 | " es.index(index=INDEX_NAME, id=doc_id, body=doc)\n", 254 | " time.sleep(1)\n" 255 | ] 256 | }, 257 | { 258 | "cell_type": "markdown", 259 | "metadata": {}, 260 | "source": [ 261 | "## 7. 삽입된 문서 확인" 262 | ] 263 | }, 264 | { 265 | "cell_type": "code", 266 | "execution_count": 41, 267 | "metadata": {}, 268 | "outputs": [ 269 | { 270 | "name": "stdout", 271 | "output_type": "stream", 272 | "text": [ 273 | "{'_id': '1',\n", 274 | " '_index': 'toy_index',\n", 275 | " '_primary_term': 1,\n", 276 | " '_seq_no': 0,\n", 277 | " '_source': {'content': '사랑해 그 말은 무엇보다 아픈 말 숨죽여서 하는 말 이젠 하기 힘든 말',\n", 278 | " 'title': 'My Love'},\n", 279 | " '_type': '_doc',\n", 280 | " '_version': 1,\n", 281 | " 'found': True}\n" 282 | ] 283 | } 284 | ], 285 | "source": [ 286 | "doc = es.get(index=INDEX_NAME, id=1)\n", 287 | "pprint.pprint(doc)" 288 | ] 289 | }, 290 | { 291 | "cell_type": "markdown", 292 | "metadata": {}, 293 | "source": [ 294 | "## 8. Term vector 확인" 295 | ] 296 | }, 297 | { 298 | "cell_type": "code", 299 | "execution_count": 42, 300 | "metadata": {}, 301 | "outputs": [], 302 | "source": [ 303 | "tv = es.termvectors(index=INDEX_NAME, id=2,body={\"fields\" : [\"content\",\"title\"]})" 304 | ] 305 | }, 306 | { 307 | "cell_type": "code", 308 | "execution_count": 43, 309 | "metadata": {}, 310 | "outputs": [ 311 | { 312 | "name": "stdout", 313 | "output_type": "stream", 314 | "text": [ 315 | "{'_id': '2',\n", 316 | " '_index': 'toy_index',\n", 317 | " '_type': '_doc',\n", 318 | " '_version': 1,\n", 319 | " 'found': True,\n", 320 | " 'term_vectors': {'content': {'field_statistics': {'doc_count': 5,\n", 321 | " 'sum_doc_freq': 375,\n", 322 | " 'sum_ttf': 425},\n", 323 | " 'terms': {'ᆫ': {'term_freq': 3,\n", 324 | " 'tokens': [{'end_offset': 36,\n", 325 | " 'position': 21,\n", 326 | " 'start_offset': 35},\n", 327 | " {'end_offset': 64,\n", 328 | " 'position': 35,\n", 329 | " 'start_offset': 62},\n", 330 | " {'end_offset': 77,\n", 331 | " 'position': 42,\n", 332 | " 'start_offset': 76}]},\n", 333 | " 'ᆫ 그리움': {'term_freq': 1,\n", 334 | " 'tokens': [{'end_offset': 68,\n", 335 | " 'position': 35,\n", 336 | " 'start_offset': 62}]},\n", 337 | " 'ᆫ 깊': {'term_freq': 1,\n", 338 | " 'tokens': [{'end_offset': 38,\n", 339 | " 'position': 21,\n", 340 | " 'start_offset': 35}]},\n", 341 | " 'ᆫ 채': {'term_freq': 1,\n", 342 | " 'tokens': [{'end_offset': 78,\n", 343 | " 'position': 42,\n", 344 | " 'start_offset': 76}]},\n", 345 | " 'ㄴ': {'term_freq': 1,\n", 346 | " 'tokens': [{'end_offset': 5,\n", 347 | " 'position': 2,\n", 348 | " 'start_offset': 3}]},\n", 349 | " 'ㄴ 스치': {'term_freq': 1,\n", 350 | " 'tokens': [{'end_offset': 8,\n", 351 | " 'position': 2,\n", 352 | " 'start_offset': 3}]},\n", 353 | " '가': {'term_freq': 1,\n", 354 | " 'tokens': [{'end_offset': 9,\n", 355 | " 'position': 5,\n", 356 | " 'start_offset': 8}]},\n", 357 | " '가 나요': {'term_freq': 1,\n", 358 | " 'tokens': [{'end_offset': 11,\n", 359 | " 'position': 5,\n", 360 | " 'start_offset': 8}]},\n", 361 | " '가슴': {'term_freq': 1,\n", 362 | " 'tokens': [{'end_offset': 73,\n", 363 | " 'position': 39,\n", 364 | " 'start_offset': 71}]},\n", 365 | " '가슴 텅': {'term_freq': 1,\n", 366 | " 'tokens': [{'end_offset': 75,\n", 367 | " 'position': 39,\n", 368 | " 'start_offset': 71}]},\n", 369 | " '견디': {'term_freq': 1,\n", 370 | " 'tokens': [{'end_offset': 50,\n", 371 | " 'position': 27,\n", 372 | " 'start_offset': 48}]},\n", 373 | " '견디 며': {'term_freq': 1,\n", 374 | " 'tokens': [{'end_offset': 51,\n", 375 | " 'position': 27,\n", 376 | " 'start_offset': 48}]},\n", 377 | " '그것': {'term_freq': 1,\n", 378 | " 'tokens': [{'end_offset': 95,\n", 379 | " 'position': 53,\n", 380 | " 'start_offset': 93}]},\n", 381 | " '그것 만': {'term_freq': 1,\n", 382 | " 'tokens': [{'end_offset': 96,\n", 383 | " 'position': 53,\n", 384 | " 'start_offset': 93}]},\n", 385 | " '그대': {'term_freq': 1,\n", 386 | " 'tokens': [{'end_offset': 85,\n", 387 | " 'position': 46,\n", 388 | " 'start_offset': 83}]},\n", 389 | " '그대 를': {'term_freq': 1,\n", 390 | " 'tokens': [{'end_offset': 86,\n", 391 | " 'position': 46,\n", 392 | " 'start_offset': 83}]},\n", 393 | " '그리움': {'term_freq': 1,\n", 394 | " 'tokens': [{'end_offset': 68,\n", 395 | " 'position': 36,\n", 396 | " 'start_offset': 65}]},\n", 397 | " '그리움 속': {'term_freq': 1,\n", 398 | " 'tokens': [{'end_offset': 69,\n", 399 | " 'position': 36,\n", 400 | " 'start_offset': 65}]},\n", 401 | " '기억': {'term_freq': 1,\n", 402 | " 'tokens': [{'end_offset': 14,\n", 403 | " 'position': 7,\n", 404 | " 'start_offset': 12}]},\n", 405 | " '기억 넘': {'term_freq': 1,\n", 406 | " 'tokens': [{'end_offset': 15,\n", 407 | " 'position': 7,\n", 408 | " 'start_offset': 12}]},\n", 409 | " '깊': {'term_freq': 1,\n", 410 | " 'tokens': [{'end_offset': 38,\n", 411 | " 'position': 22,\n", 412 | " 'start_offset': 37}]},\n", 413 | " '깊 은': {'term_freq': 1,\n", 414 | " 'tokens': [{'end_offset': 39,\n", 415 | " 'position': 22,\n", 416 | " 'start_offset': 37}]},\n", 417 | " '끝내어': {'term_freq': 1,\n", 418 | " 'tokens': [{'end_offset': 2,\n", 419 | " 'position': 0,\n", 420 | " 'start_offset': 0}]},\n", 421 | " '끝내어 우리': {'term_freq': 1,\n", 422 | " 'tokens': [{'end_offset': 5,\n", 423 | " 'position': 0,\n", 424 | " 'start_offset': 0}]},\n", 425 | " '나': {'term_freq': 2,\n", 426 | " 'tokens': [{'end_offset': 30,\n", 427 | " 'position': 16,\n", 428 | " 'start_offset': 28},\n", 429 | " {'end_offset': 103,\n", 430 | " 'position': 58,\n", 431 | " 'start_offset': 102}]},\n", 432 | " '나 에게': {'term_freq': 1,\n", 433 | " 'tokens': [{'end_offset': 30,\n", 434 | " 'position': 16,\n", 435 | " 'start_offset': 28}]},\n", 436 | " '나 후회': {'term_freq': 1,\n", 437 | " 'tokens': [{'end_offset': 106,\n", 438 | " 'position': 58,\n", 439 | " 'start_offset': 102}]},\n", 440 | " '나요': {'term_freq': 2,\n", 441 | " 'tokens': [{'end_offset': 11,\n", 442 | " 'position': 6,\n", 443 | " 'start_offset': 9},\n", 444 | " {'end_offset': 27,\n", 445 | " 'position': 15,\n", 446 | " 'start_offset': 25}]},\n", 447 | " '나요 기억': {'term_freq': 1,\n", 448 | " 'tokens': [{'end_offset': 14,\n", 449 | " 'position': 6,\n", 450 | " 'start_offset': 9}]},\n", 451 | " '나요 나': {'term_freq': 1,\n", 452 | " 'tokens': [{'end_offset': 30,\n", 453 | " 'position': 15,\n", 454 | " 'start_offset': 25}]},\n", 455 | " '넘': {'term_freq': 1,\n", 456 | " 'tokens': [{'end_offset': 15,\n", 457 | " 'position': 8,\n", 458 | " 'start_offset': 14}]},\n", 459 | " '넘 어': {'term_freq': 1,\n", 460 | " 'tokens': [{'end_offset': 16,\n", 461 | " 'position': 8,\n", 462 | " 'start_offset': 14}]},\n", 463 | " '는': {'term_freq': 1,\n", 464 | " 'tokens': [{'end_offset': 43,\n", 465 | " 'position': 25,\n", 466 | " 'start_offset': 42}]},\n", 467 | " '는 어떻게': {'term_freq': 1,\n", 468 | " 'tokens': [{'end_offset': 47,\n", 469 | " 'position': 25,\n", 470 | " 'start_offset': 42}]},\n", 471 | " '는지': {'term_freq': 1,\n", 472 | " 'tokens': [{'end_offset': 58,\n", 473 | " 'position': 32,\n", 474 | " 'start_offset': 56}]},\n", 475 | " '는지 매일': {'term_freq': 1,\n", 476 | " 'tokens': [{'end_offset': 61,\n", 477 | " 'position': 32,\n", 478 | " 'start_offset': 56}]},\n", 479 | " '던': {'term_freq': 1,\n", 480 | " 'tokens': [{'end_offset': 91,\n", 481 | " 'position': 51,\n", 482 | " 'start_offset': 90}]},\n", 483 | " '던 일': {'term_freq': 1,\n", 484 | " 'tokens': [{'end_offset': 92,\n", 485 | " 'position': 51,\n", 486 | " 'start_offset': 90}]},\n", 487 | " '를': {'term_freq': 2,\n", 488 | " 'tokens': [{'end_offset': 20,\n", 489 | " 'position': 11,\n", 490 | " 'start_offset': 19},\n", 491 | " {'end_offset': 86,\n", 492 | " 'position': 47,\n", 493 | " 'start_offset': 85}]},\n", 494 | " '를 사랑': {'term_freq': 1,\n", 495 | " 'tokens': [{'end_offset': 89,\n", 496 | " 'position': 47,\n", 497 | " 'start_offset': 85}]},\n", 498 | " '를 지우': {'term_freq': 1,\n", 499 | " 'tokens': [{'end_offset': 24,\n", 500 | " 'position': 11,\n", 501 | " 'start_offset': 19}]},\n", 502 | " '만': {'term_freq': 1,\n", 503 | " 'tokens': [{'end_offset': 96,\n", 504 | " 'position': 54,\n", 505 | " 'start_offset': 95}]},\n", 506 | " '만 은': {'term_freq': 1,\n", 507 | " 'tokens': [{'end_offset': 97,\n", 508 | " 'position': 54,\n", 509 | " 'start_offset': 95}]},\n", 510 | " '매일': {'term_freq': 1,\n", 511 | " 'tokens': [{'end_offset': 61,\n", 512 | " 'position': 33,\n", 513 | " 'start_offset': 59}]},\n", 514 | " '매일 아프': {'term_freq': 1,\n", 515 | " 'tokens': [{'end_offset': 64,\n", 516 | " 'position': 33,\n", 517 | " 'start_offset': 59}]},\n", 518 | " '며': {'term_freq': 1,\n", 519 | " 'tokens': [{'end_offset': 51,\n", 520 | " 'position': 28,\n", 521 | " 'start_offset': 50}]},\n", 522 | " '며 살': {'term_freq': 1,\n", 523 | " 'tokens': [{'end_offset': 53,\n", 524 | " 'position': 28,\n", 525 | " 'start_offset': 50}]},\n", 526 | " '비': {'term_freq': 1,\n", 527 | " 'tokens': [{'end_offset': 77,\n", 528 | " 'position': 41,\n", 529 | " 'start_offset': 76}]},\n", 530 | " '비 ᆫ': {'term_freq': 1,\n", 531 | " 'tokens': [{'end_offset': 77,\n", 532 | " 'position': 41,\n", 533 | " 'start_offset': 76}]},\n", 534 | " '사랑': {'term_freq': 2,\n", 535 | " 'tokens': [{'end_offset': 33,\n", 536 | " 'position': 18,\n", 537 | " 'start_offset': 31},\n", 538 | " {'end_offset': 89,\n", 539 | " 'position': 48,\n", 540 | " 'start_offset': 87}]},\n", 541 | " '사랑 이': {'term_freq': 1,\n", 542 | " 'tokens': [{'end_offset': 34,\n", 543 | " 'position': 18,\n", 544 | " 'start_offset': 31}]},\n", 545 | " '사랑 하': {'term_freq': 1,\n", 546 | " 'tokens': [{'end_offset': 90,\n", 547 | " 'position': 48,\n", 548 | " 'start_offset': 87}]},\n", 549 | " '살': {'term_freq': 2,\n", 550 | " 'tokens': [{'end_offset': 53,\n", 551 | " 'position': 29,\n", 552 | " 'start_offset': 52},\n", 553 | " {'end_offset': 80,\n", 554 | " 'position': 44,\n", 555 | " 'start_offset': 79}]},\n", 556 | " '살 아도': {'term_freq': 1,\n", 557 | " 'tokens': [{'end_offset': 82,\n", 558 | " 'position': 44,\n", 559 | " 'start_offset': 79}]},\n", 560 | " '살 아야': {'term_freq': 1,\n", 561 | " 'tokens': [{'end_offset': 55,\n", 562 | " 'position': 29,\n", 563 | " 'start_offset': 52}]},\n", 564 | " '상처': {'term_freq': 1,\n", 565 | " 'tokens': [{'end_offset': 42,\n", 566 | " 'position': 24,\n", 567 | " 'start_offset': 40}]},\n", 568 | " '상처 는': {'term_freq': 1,\n", 569 | " 'tokens': [{'end_offset': 43,\n", 570 | " 'position': 24,\n", 571 | " 'start_offset': 40}]},\n", 572 | " '서로': {'term_freq': 1,\n", 573 | " 'tokens': [{'end_offset': 19,\n", 574 | " 'position': 10,\n", 575 | " 'start_offset': 17}]},\n", 576 | " '서로 를': {'term_freq': 1,\n", 577 | " 'tokens': [{'end_offset': 20,\n", 578 | " 'position': 10,\n", 579 | " 'start_offset': 17}]},\n", 580 | " '속': {'term_freq': 1,\n", 581 | " 'tokens': [{'end_offset': 69,\n", 582 | " 'position': 37,\n", 583 | " 'start_offset': 68}]},\n", 584 | " '속 에': {'term_freq': 1,\n", 585 | " 'tokens': [{'end_offset': 70,\n", 586 | " 'position': 37,\n", 587 | " 'start_offset': 68}]},\n", 588 | " '스치': {'term_freq': 1,\n", 589 | " 'tokens': [{'end_offset': 8,\n", 590 | " 'position': 3,\n", 591 | " 'start_offset': 6}]},\n", 592 | " '스치 어': {'term_freq': 1,\n", 593 | " 'tokens': [{'end_offset': 8,\n", 594 | " 'position': 3,\n", 595 | " 'start_offset': 6}]},\n", 596 | " '아도': {'term_freq': 1,\n", 597 | " 'tokens': [{'end_offset': 82,\n", 598 | " 'position': 45,\n", 599 | " 'start_offset': 80}]},\n", 600 | " '아도 그대': {'term_freq': 1,\n", 601 | " 'tokens': [{'end_offset': 85,\n", 602 | " 'position': 45,\n", 603 | " 'start_offset': 80}]},\n", 604 | " '아야': {'term_freq': 1,\n", 605 | " 'tokens': [{'end_offset': 55,\n", 606 | " 'position': 30,\n", 607 | " 'start_offset': 53}]},\n", 608 | " '아야 하': {'term_freq': 1,\n", 609 | " 'tokens': [{'end_offset': 56,\n", 610 | " 'position': 30,\n", 611 | " 'start_offset': 53}]},\n", 612 | " '아요': {'term_freq': 1,\n", 613 | " 'tokens': [{'end_offset': 111,\n", 614 | " 'position': 63,\n", 615 | " 'start_offset': 109}]},\n", 616 | " '아프': {'term_freq': 1,\n", 617 | " 'tokens': [{'end_offset': 64,\n", 618 | " 'position': 34,\n", 619 | " 'start_offset': 62}]},\n", 620 | " '아프 ᆫ': {'term_freq': 1,\n", 621 | " 'tokens': [{'end_offset': 64,\n", 622 | " 'position': 34,\n", 623 | " 'start_offset': 62}]},\n", 624 | " '않': {'term_freq': 2,\n", 625 | " 'tokens': [{'end_offset': 25,\n", 626 | " 'position': 14,\n", 627 | " 'start_offset': 24},\n", 628 | " {'end_offset': 109,\n", 629 | " 'position': 62,\n", 630 | " 'start_offset': 108}]},\n", 631 | " '않 나요': {'term_freq': 1,\n", 632 | " 'tokens': [{'end_offset': 27,\n", 633 | " 'position': 14,\n", 634 | " 'start_offset': 24}]},\n", 635 | " '않 아요': {'term_freq': 1,\n", 636 | " 'tokens': [{'end_offset': 111,\n", 637 | " 'position': 62,\n", 638 | " 'start_offset': 108}]},\n", 639 | " '았': {'term_freq': 1,\n", 640 | " 'tokens': [{'end_offset': 90,\n", 641 | " 'position': 50,\n", 642 | " 'start_offset': 89}]},\n", 643 | " '았 던': {'term_freq': 1,\n", 644 | " 'tokens': [{'end_offset': 91,\n", 645 | " 'position': 50,\n", 646 | " 'start_offset': 89}]},\n", 647 | " '어': {'term_freq': 2,\n", 648 | " 'tokens': [{'end_offset': 8,\n", 649 | " 'position': 4,\n", 650 | " 'start_offset': 6},\n", 651 | " {'end_offset': 16,\n", 652 | " 'position': 9,\n", 653 | " 'start_offset': 15}]},\n", 654 | " '어 가': {'term_freq': 1,\n", 655 | " 'tokens': [{'end_offset': 9,\n", 656 | " 'position': 4,\n", 657 | " 'start_offset': 6}]},\n", 658 | " '어 서로': {'term_freq': 1,\n", 659 | " 'tokens': [{'end_offset': 19,\n", 660 | " 'position': 9,\n", 661 | " 'start_offset': 15}]},\n", 662 | " '어도': {'term_freq': 1,\n", 663 | " 'tokens': [{'end_offset': 101,\n", 664 | " 'position': 57,\n", 665 | " 'start_offset': 99}]},\n", 666 | " '어도 나': {'term_freq': 1,\n", 667 | " 'tokens': [{'end_offset': 103,\n", 668 | " 'position': 57,\n", 669 | " 'start_offset': 99}]},\n", 670 | " '어떻게': {'term_freq': 1,\n", 671 | " 'tokens': [{'end_offset': 47,\n", 672 | " 'position': 26,\n", 673 | " 'start_offset': 44}]},\n", 674 | " '어떻게 견디': {'term_freq': 1,\n", 675 | " 'tokens': [{'end_offset': 50,\n", 676 | " 'position': 26,\n", 677 | " 'start_offset': 44}]},\n", 678 | " '어야': {'term_freq': 1,\n", 679 | " 'tokens': [{'end_offset': 24,\n", 680 | " 'position': 13,\n", 681 | " 'start_offset': 21}]},\n", 682 | " '어야 않': {'term_freq': 1,\n", 683 | " 'tokens': [{'end_offset': 25,\n", 684 | " 'position': 13,\n", 685 | " 'start_offset': 21}]},\n", 686 | " '에': {'term_freq': 1,\n", 687 | " 'tokens': [{'end_offset': 70,\n", 688 | " 'position': 38,\n", 689 | " 'start_offset': 69}]},\n", 690 | " '에 가슴': {'term_freq': 1,\n", 691 | " 'tokens': [{'end_offset': 73,\n", 692 | " 'position': 38,\n", 693 | " 'start_offset': 69}]},\n", 694 | " '에게': {'term_freq': 1,\n", 695 | " 'tokens': [{'end_offset': 30,\n", 696 | " 'position': 17,\n", 697 | " 'start_offset': 28}]},\n", 698 | " '에게 사랑': {'term_freq': 1,\n", 699 | " 'tokens': [{'end_offset': 33,\n", 700 | " 'position': 17,\n", 701 | " 'start_offset': 28}]},\n", 702 | " '우리': {'term_freq': 1,\n", 703 | " 'tokens': [{'end_offset': 5,\n", 704 | " 'position': 1,\n", 705 | " 'start_offset': 3}]},\n", 706 | " '우리 ㄴ': {'term_freq': 1,\n", 707 | " 'tokens': [{'end_offset': 5,\n", 708 | " 'position': 1,\n", 709 | " 'start_offset': 3}]},\n", 710 | " '은': {'term_freq': 2,\n", 711 | " 'tokens': [{'end_offset': 39,\n", 712 | " 'position': 23,\n", 713 | " 'start_offset': 38},\n", 714 | " {'end_offset': 97,\n", 715 | " 'position': 55,\n", 716 | " 'start_offset': 96}]},\n", 717 | " '은 상처': {'term_freq': 1,\n", 718 | " 'tokens': [{'end_offset': 42,\n", 719 | " 'position': 23,\n", 720 | " 'start_offset': 38}]},\n", 721 | " '은 죽': {'term_freq': 1,\n", 722 | " 'tokens': [{'end_offset': 99,\n", 723 | " 'position': 55,\n", 724 | " 'start_offset': 96}]},\n", 725 | " '이': {'term_freq': 1,\n", 726 | " 'tokens': [{'end_offset': 34,\n", 727 | " 'position': 19,\n", 728 | " 'start_offset': 33}]},\n", 729 | " '이 주': {'term_freq': 1,\n", 730 | " 'tokens': [{'end_offset': 36,\n", 731 | " 'position': 19,\n", 732 | " 'start_offset': 33}]},\n", 733 | " '일': {'term_freq': 1,\n", 734 | " 'tokens': [{'end_offset': 92,\n", 735 | " 'position': 52,\n", 736 | " 'start_offset': 91}]},\n", 737 | " '일 그것': {'term_freq': 1,\n", 738 | " 'tokens': [{'end_offset': 95,\n", 739 | " 'position': 52,\n", 740 | " 'start_offset': 91}]},\n", 741 | " '주': {'term_freq': 1,\n", 742 | " 'tokens': [{'end_offset': 36,\n", 743 | " 'position': 20,\n", 744 | " 'start_offset': 35}]},\n", 745 | " '주 ᆫ': {'term_freq': 1,\n", 746 | " 'tokens': [{'end_offset': 36,\n", 747 | " 'position': 20,\n", 748 | " 'start_offset': 35}]},\n", 749 | " '죽': {'term_freq': 1,\n", 750 | " 'tokens': [{'end_offset': 99,\n", 751 | " 'position': 56,\n", 752 | " 'start_offset': 98}]},\n", 753 | " '죽 어도': {'term_freq': 1,\n", 754 | " 'tokens': [{'end_offset': 101,\n", 755 | " 'position': 56,\n", 756 | " 'start_offset': 98}]},\n", 757 | " '지': {'term_freq': 1,\n", 758 | " 'tokens': [{'end_offset': 108,\n", 759 | " 'position': 61,\n", 760 | " 'start_offset': 107}]},\n", 761 | " '지 않': {'term_freq': 1,\n", 762 | " 'tokens': [{'end_offset': 109,\n", 763 | " 'position': 61,\n", 764 | " 'start_offset': 107}]},\n", 765 | " '지우': {'term_freq': 1,\n", 766 | " 'tokens': [{'end_offset': 24,\n", 767 | " 'position': 12,\n", 768 | " 'start_offset': 21}]},\n", 769 | " '지우 어야': {'term_freq': 1,\n", 770 | " 'tokens': [{'end_offset': 24,\n", 771 | " 'position': 12,\n", 772 | " 'start_offset': 21}]},\n", 773 | " '채': {'term_freq': 1,\n", 774 | " 'tokens': [{'end_offset': 78,\n", 775 | " 'position': 43,\n", 776 | " 'start_offset': 77}]},\n", 777 | " '채 살': {'term_freq': 1,\n", 778 | " 'tokens': [{'end_offset': 80,\n", 779 | " 'position': 43,\n", 780 | " 'start_offset': 77}]},\n", 781 | " '텅': {'term_freq': 1,\n", 782 | " 'tokens': [{'end_offset': 75,\n", 783 | " 'position': 40,\n", 784 | " 'start_offset': 74}]},\n", 785 | " '텅 비': {'term_freq': 1,\n", 786 | " 'tokens': [{'end_offset': 77,\n", 787 | " 'position': 40,\n", 788 | " 'start_offset': 74}]},\n", 789 | " '하': {'term_freq': 3,\n", 790 | " 'tokens': [{'end_offset': 56,\n", 791 | " 'position': 31,\n", 792 | " 'start_offset': 55},\n", 793 | " {'end_offset': 90,\n", 794 | " 'position': 49,\n", 795 | " 'start_offset': 89},\n", 796 | " {'end_offset': 107,\n", 797 | " 'position': 60,\n", 798 | " 'start_offset': 106}]},\n", 799 | " '하 는지': {'term_freq': 1,\n", 800 | " 'tokens': [{'end_offset': 58,\n", 801 | " 'position': 31,\n", 802 | " 'start_offset': 55}]},\n", 803 | " '하 았': {'term_freq': 1,\n", 804 | " 'tokens': [{'end_offset': 90,\n", 805 | " 'position': 49,\n", 806 | " 'start_offset': 89}]},\n", 807 | " '하 지': {'term_freq': 1,\n", 808 | " 'tokens': [{'end_offset': 108,\n", 809 | " 'position': 60,\n", 810 | " 'start_offset': 106}]},\n", 811 | " '후회': {'term_freq': 1,\n", 812 | " 'tokens': [{'end_offset': 106,\n", 813 | " 'position': 59,\n", 814 | " 'start_offset': 104}]},\n", 815 | " '후회 하': {'term_freq': 1,\n", 816 | " 'tokens': [{'end_offset': 107,\n", 817 | " 'position': 59,\n", 818 | " 'start_offset': 104}]}}},\n", 819 | " 'title': {'field_statistics': {'doc_count': 5,\n", 820 | " 'sum_doc_freq': 23,\n", 821 | " 'sum_ttf': 23},\n", 822 | " 'terms': {'고': {'term_freq': 1,\n", 823 | " 'tokens': [{'end_offset': 2,\n", 824 | " 'position': 1,\n", 825 | " 'start_offset': 1}]},\n", 826 | " '고 있': {'term_freq': 1,\n", 827 | " 'tokens': [{'end_offset': 3,\n", 828 | " 'position': 1,\n", 829 | " 'start_offset': 1}]},\n", 830 | " '나요': {'term_freq': 1,\n", 831 | " 'tokens': [{'end_offset': 5,\n", 832 | " 'position': 3,\n", 833 | " 'start_offset': 3}]},\n", 834 | " '들': {'term_freq': 1,\n", 835 | " 'tokens': [{'end_offset': 1,\n", 836 | " 'position': 0,\n", 837 | " 'start_offset': 0}]},\n", 838 | " '들 고': {'term_freq': 1,\n", 839 | " 'tokens': [{'end_offset': 2,\n", 840 | " 'position': 0,\n", 841 | " 'start_offset': 0}]},\n", 842 | " '있': {'term_freq': 1,\n", 843 | " 'tokens': [{'end_offset': 3,\n", 844 | " 'position': 2,\n", 845 | " 'start_offset': 2}]},\n", 846 | " '있 나요': {'term_freq': 1,\n", 847 | " 'tokens': [{'end_offset': 5,\n", 848 | " 'position': 2,\n", 849 | " 'start_offset': 2}]}}}},\n", 850 | " 'took': 3}\n" 851 | ] 852 | } 853 | ], 854 | "source": [ 855 | "pprint.pprint(tv)" 856 | ] 857 | }, 858 | { 859 | "cell_type": "markdown", 860 | "metadata": {}, 861 | "source": [ 862 | "## 7. 검색" 863 | ] 864 | }, 865 | { 866 | "cell_type": "code", 867 | "execution_count": 45, 868 | "metadata": {}, 869 | "outputs": [ 870 | { 871 | "name": "stdout", 872 | "output_type": "stream", 873 | "text": [ 874 | "{'tokens': [{'end_offset': 2,\n", 875 | " 'position': 0,\n", 876 | " 'start_offset': 0,\n", 877 | " 'token': '사랑',\n", 878 | " 'type': 'word'},\n", 879 | " {'end_offset': 3,\n", 880 | " 'position': 0,\n", 881 | " 'positionLength': 2,\n", 882 | " 'start_offset': 0,\n", 883 | " 'token': '사랑 하',\n", 884 | " 'type': 'shingle'},\n", 885 | " {'end_offset': 3,\n", 886 | " 'position': 1,\n", 887 | " 'start_offset': 2,\n", 888 | " 'token': '하',\n", 889 | " 'type': 'word'},\n", 890 | " {'end_offset': 5,\n", 891 | " 'position': 1,\n", 892 | " 'positionLength': 2,\n", 893 | " 'start_offset': 2,\n", 894 | " 'token': '하 지만',\n", 895 | " 'type': 'shingle'},\n", 896 | " {'end_offset': 5,\n", 897 | " 'position': 2,\n", 898 | " 'start_offset': 3,\n", 899 | " 'token': '지만',\n", 900 | " 'type': 'word'},\n", 901 | " {'end_offset': 8,\n", 902 | " 'position': 2,\n", 903 | " 'positionLength': 2,\n", 904 | " 'start_offset': 3,\n", 905 | " 'token': '지만 힘들',\n", 906 | " 'type': 'shingle'},\n", 907 | " {'end_offset': 8,\n", 908 | " 'position': 3,\n", 909 | " 'start_offset': 6,\n", 910 | " 'token': '힘들',\n", 911 | " 'type': 'word'},\n", 912 | " {'end_offset': 9,\n", 913 | " 'position': 3,\n", 914 | " 'positionLength': 2,\n", 915 | " 'start_offset': 6,\n", 916 | " 'token': '힘들 어',\n", 917 | " 'type': 'shingle'},\n", 918 | " {'end_offset': 9,\n", 919 | " 'position': 4,\n", 920 | " 'start_offset': 8,\n", 921 | " 'token': '어',\n", 922 | " 'type': 'word'},\n", 923 | " {'end_offset': 11,\n", 924 | " 'position': 4,\n", 925 | " 'positionLength': 2,\n", 926 | " 'start_offset': 8,\n", 927 | " 'token': '어 죽',\n", 928 | " 'type': 'shingle'},\n", 929 | " {'end_offset': 11,\n", 930 | " 'position': 5,\n", 931 | " 'start_offset': 10,\n", 932 | " 'token': '죽',\n", 933 | " 'type': 'word'},\n", 934 | " {'end_offset': 12,\n", 935 | " 'position': 5,\n", 936 | " 'positionLength': 2,\n", 937 | " 'start_offset': 10,\n", 938 | " 'token': '죽 겠',\n", 939 | " 'type': 'shingle'},\n", 940 | " {'end_offset': 12,\n", 941 | " 'position': 6,\n", 942 | " 'start_offset': 11,\n", 943 | " 'token': '겠',\n", 944 | " 'type': 'word'},\n", 945 | " {'end_offset': 13,\n", 946 | " 'position': 6,\n", 947 | " 'positionLength': 2,\n", 948 | " 'start_offset': 11,\n", 949 | " 'token': '겠 네',\n", 950 | " 'type': 'shingle'},\n", 951 | " {'end_offset': 13,\n", 952 | " 'position': 7,\n", 953 | " 'start_offset': 12,\n", 954 | " 'token': '네',\n", 955 | " 'type': 'word'}]}\n" 956 | ] 957 | } 958 | ], 959 | "source": [ 960 | "query = \"사랑하지만 힘들어 죽겠네\"\n", 961 | "res = es.indices.analyze(index=INDEX_NAME,\n", 962 | " body={\n", 963 | " \"analyzer\" : \"korean\",\n", 964 | " \"text\" : query\n", 965 | " }\n", 966 | " )\n", 967 | "pprint.pprint(res)" 968 | ] 969 | }, 970 | { 971 | "cell_type": "code", 972 | "execution_count": 46, 973 | "metadata": {}, 974 | "outputs": [ 975 | { 976 | "name": "stdout", 977 | "output_type": "stream", 978 | "text": [ 979 | "{'_shards': {'failed': 0, 'skipped': 0, 'successful': 1, 'total': 1},\n", 980 | " 'hits': {'hits': [{'_id': '2',\n", 981 | " '_index': 'toy_index',\n", 982 | " '_score': 3.3773398,\n", 983 | " '_source': {'content': '끝내 우린 스쳐가나요 기억넘어 서로를 지워야하나요 내게 사랑이 '\n", 984 | " '준 깊은 상처는 어떻게 견디며 살아야하는지 매일 아픈 '\n", 985 | " '그리움속에 가슴 텅 빈채 살아도 그대를 사랑했던일 그것만은 '\n", 986 | " '죽어도 나 후회하지않아요',\n", 987 | " 'title': '듣고있나요'},\n", 988 | " '_type': '_doc'},\n", 989 | " {'_id': '3',\n", 990 | " '_index': 'toy_index',\n", 991 | " '_score': 3.2812269,\n", 992 | " '_source': {'content': '눈을 떠 바라보아요 그댄 정말 가셨나요 단 한번 보내준 그대 '\n", 993 | " '눈빛은 날 사랑했나요 또 다른 사랑이 와도 이젠 쉽게 허락되진 '\n", 994 | " '않아 견디기 힘들어 운명같은 우연을',\n", 995 | " 'title': '인연'},\n", 996 | " '_type': '_doc'},\n", 997 | " {'_id': '1',\n", 998 | " '_index': 'toy_index',\n", 999 | " '_score': 2.3294225,\n", 1000 | " '_source': {'content': '사랑해 그 말은 무엇보다 아픈 말 숨죽여서 하는 말 이젠 하기 '\n", 1001 | " '힘든 말',\n", 1002 | " 'title': 'My Love'},\n", 1003 | " '_type': '_doc'},\n", 1004 | " {'_id': '4',\n", 1005 | " '_index': 'toy_index',\n", 1006 | " '_score': 0.8140714,\n", 1007 | " '_source': {'content': ['얼마나 더 견뎌야 하는지 짙은 어둠을 헤메고 있어',\n", 1008 | " '내가 바란 꿈이라는 것은 없는걸까',\n", 1009 | " '더 이상은 견딜수 없는 것'],\n", 1010 | " 'title': '말리 꽃'},\n", 1011 | " '_type': '_doc'},\n", 1012 | " {'_id': '5',\n", 1013 | " '_index': 'toy_index',\n", 1014 | " '_score': 0.14223012,\n", 1015 | " '_source': {'content': '천번 번이고 다시 태어난대도 그런 사람 또 없을테죠 슬픈 '\n", 1016 | " '내삶을 따뜻하게 해준 잠 고마운 사람입니다',\n", 1017 | " 'title': '그런 사람 또 없습니다'},\n", 1018 | " '_type': '_doc'}],\n", 1019 | " 'max_score': 3.3773398,\n", 1020 | " 'total': {'relation': 'eq', 'value': 5}},\n", 1021 | " 'timed_out': False,\n", 1022 | " 'took': 10}\n" 1023 | ] 1024 | } 1025 | ], 1026 | "source": [ 1027 | "query = \"사랑하지만 힘들어 죽겠네\"\n", 1028 | "res = es.search(index=INDEX_NAME, q=query, size=10)\n", 1029 | "pprint.pprint(res)" 1030 | ] 1031 | }, 1032 | { 1033 | "cell_type": "code", 1034 | "execution_count": 47, 1035 | "metadata": {}, 1036 | "outputs": [ 1037 | { 1038 | "name": "stdout", 1039 | "output_type": "stream", 1040 | "text": [ 1041 | "Doc ID: '2' Score: 3.38\n", 1042 | "Doc ID: '3' Score: 3.28\n", 1043 | "Doc ID: '1' Score: 2.33\n", 1044 | "Doc ID: '4' Score: 0.81\n", 1045 | "Doc ID: '5' Score: 0.14\n" 1046 | ] 1047 | } 1048 | ], 1049 | "source": [ 1050 | "for hit in res['hits']['hits']:\n", 1051 | " print(\"Doc ID: %3r Score: %5.2f\" % (hit['_id'], hit['_score']))\n" 1052 | ] 1053 | }, 1054 | { 1055 | "cell_type": "code", 1056 | "execution_count": null, 1057 | "metadata": {}, 1058 | "outputs": [], 1059 | "source": [] 1060 | } 1061 | ], 1062 | "metadata": { 1063 | "kernelspec": { 1064 | "display_name": "Python 3 (ipykernel)", 1065 | "language": "python", 1066 | "name": "python3" 1067 | }, 1068 | "language_info": { 1069 | "codemirror_mode": { 1070 | "name": "ipython", 1071 | "version": 3 1072 | }, 1073 | "file_extension": ".py", 1074 | "mimetype": "text/x-python", 1075 | "name": "python", 1076 | "nbconvert_exporter": "python", 1077 | "pygments_lexer": "ipython3", 1078 | "version": "3.8.5" 1079 | } 1080 | }, 1081 | "nbformat": 4, 1082 | "nbformat_minor": 4 1083 | } 1084 | --------------------------------------------------------------------------------