Merge branch 'apiPR' into 'master'
search pr 까먹고 pull 안하고 branch push 했으니ㅜㅜ search만 merge 부탁합니다. See merge request !7
Showing
1 changed file
with
172 additions
and
0 deletions
api/search.js
0 → 100644
| 1 | +const rp = require("request-promise"); | ||
| 2 | +const cheerio = require("cheerio"); | ||
| 3 | +const Entities = require('html-entities').XmlEntities; | ||
| 4 | +const machineRead = require('./machineRead'); | ||
| 5 | + | ||
| 6 | +const entities = new Entities(); | ||
| 7 | + | ||
| 8 | +const searchURL = { | ||
| 9 | + "naver" : "https://search.naver.com/search.naver?", | ||
| 10 | + "google" : "https://www.google.com/search?" | ||
| 11 | +} | ||
| 12 | + | ||
| 13 | +/** | ||
| 14 | + * @param {string} keywordText 검색할 키워드 | ||
| 15 | + * @param {cheerio} $ cheerio임. | ||
| 16 | + * @param {string} elem 내용을 찾을 html의 selector인거같음 | ||
| 17 | + * @return {boolean} 찾았다면 true | ||
| 18 | + * @description 주어진 html의 selector에서 keyword의 내용을 찾아 여부를 반환 | ||
| 19 | + */ | ||
| 20 | +const keywordChecking = ( keywordText, $, elem ) => { | ||
| 21 | + let tempCheck = false; | ||
| 22 | + keywordText.split( ' ' ).forEach( ( Word ) => { | ||
| 23 | + if( $( elem ).text().indexOf( Word ) !== -1 ) { | ||
| 24 | + tempCheck = true; | ||
| 25 | + } | ||
| 26 | + }); | ||
| 27 | + | ||
| 28 | + if( tempCheck ) { | ||
| 29 | + return true; | ||
| 30 | + } | ||
| 31 | + return false; | ||
| 32 | +} | ||
| 33 | + | ||
| 34 | +/** | ||
| 35 | + * @param {Object} searchResult 검색된 내용이 담긴 빈 오브젝트 | ||
| 36 | + * @param {cheerio} $ cheerio임. | ||
| 37 | + * @param {string} elem 내용을 찾을 html의 selector인거같음 | ||
| 38 | + * @param {string} defaultURL url이 없을 경우 달아주는 비상용 원래 링크 | ||
| 39 | + * @description 구글용 title passage 찾기함수 | ||
| 40 | + */ | ||
| 41 | +const google = ( searchResult, $, elem , defaultURL ) => { | ||
| 42 | + searchResult.passage = entities.decode( $( elem ).parent().parent().parent().text()).trim(), | ||
| 43 | + searchResult.url = decodeURIComponent( $( elem ).attr( "href" ) ); | ||
| 44 | + searchResult.title = entities.decode( $( elem ).children("div").text() ); // title 캐오기 수정 가능 | ||
| 45 | + | ||
| 46 | + if( searchResult.url.indexOf( "/url?q=" ) === 0 ) { | ||
| 47 | + searchResult.url = searchResult.url.replace( "/url?q=", "" ); | ||
| 48 | + } else if( searchResult.url.indexOf( "/search?" ) === 0 ) { | ||
| 49 | + searchResult.url = "https://google.com" + searchResult.url; | ||
| 50 | + } else { | ||
| 51 | + searchResult.url = defaultURL; | ||
| 52 | + } | ||
| 53 | +} | ||
| 54 | + | ||
| 55 | +/** | ||
| 56 | + * @param {object} searchResult 검색된 내용이 담긴 빈 오브젝트 | ||
| 57 | + * @param {cheerio} $ cheerio임. | ||
| 58 | + * @param {string} elem 내용을 찾을 html의 selector인거같음 | ||
| 59 | + * @param {string} defaultURL url이 없을 경우 달아주는 비상용 원래 링크 | ||
| 60 | + * @description 네이버용 title passage 찾기함수 | ||
| 61 | + */ | ||
| 62 | +const naver = ( searchResult, $, elem , defaultURL ) => { | ||
| 63 | + searchResult.title = $( elem ).parent().attr( "title" ); | ||
| 64 | + searchResult.passage = entities.decode( $( elem ).parent().parent().parent().text()).trim(), | ||
| 65 | + searchResult.url = $( elem ).parent().attr( "href" ); | ||
| 66 | + | ||
| 67 | + if( searchResult.url === undefined ) { | ||
| 68 | + searchResult.url = defaultURL; | ||
| 69 | + } | ||
| 70 | +} | ||
| 71 | + | ||
| 72 | +/** | ||
| 73 | + * @param {{title:string,passage:string,ulr:string}} searchResult 검색 결과가 담긴 object | ||
| 74 | + * @param {[]} result 최종 결과가 담기는 어레이 | ||
| 75 | + * @param {boolean} keywordCheck 키워드가 확인됐는지 여부 | ||
| 76 | + * @description 타이틀이 없을 경우 달아주거나 중복된 것들 제거하는 등의 역활을 해 최종적으로 결과에 담아주는 함수 | ||
| 77 | + */ | ||
| 78 | +const searchToResult = (searchResult, result, keywordCheck) => { | ||
| 79 | + searchResult.passage = searchResult.passage.replace( /(http(s)?:\/\/)([a-z0-9\w]+\.*)+[a-z0-9]{2,4}/gi, ' ' ).replace( /\s{1,}|\s{1,}|\r\n|\r|\n/g, ' ' ).trim(); | ||
| 80 | + | ||
| 81 | + if( searchResult.title === undefined || !searchResult.title.length ) { | ||
| 82 | + searchResult.title = searchResult.passage.split(' ').slice( 0, 3 ).toString().replace(/,/g,' ') + ".."; | ||
| 83 | + } else { | ||
| 84 | + searchResult.title = searchResult.title.replace( /(http(s)?:\/\/)([a-z0-9\w]+\.*)+[a-z0-9]{2,4}/gi, ' ' ).replace( /\s{1,}|\s{1,}|\r\n|\r|\n/g, ' ' ).trim(); | ||
| 85 | + searchResult.passage = searchResult.passage.replace( searchResult.title, '' ); | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + if( !result.length ) { | ||
| 89 | + if( keywordCheck ) { | ||
| 90 | + result.push( searchResult ); | ||
| 91 | + } | ||
| 92 | + } else if( keywordCheck ) { | ||
| 93 | + // 공백 제거하고 비교 | ||
| 94 | + if( result[ result.length - 1 ].passage.replace( /\s/g, '' ) !== searchResult.passage.replace( /\s/g, '' ) ) { | ||
| 95 | + result.push( searchResult ); | ||
| 96 | + } | ||
| 97 | + } | ||
| 98 | +} | ||
| 99 | + | ||
| 100 | +/** | ||
| 101 | + * @param {string} main 검색할 사이트의 메인 내용이 들어있는 셀렉터를 줘야합니다. | ||
| 102 | + * @param {string} keywordText 분석할 키워드의 내용 | ||
| 103 | + * @param {string} html html 파싱한 내용 | ||
| 104 | + * @param {string} defaultURL url이 없을 경우 달아주는 비상용 원래 링크 | ||
| 105 | + * @param {()=>{}} findSearchResult search result를 찾아주는 함수 | ||
| 106 | + * @returns {{url:string,title:string,passage:string}[]} object 여러 개를 가진 list를 준다. 각 json의 url, title, passage로 접근가능 | ||
| 107 | + * @description html을 크롤링한 데이터에서 url title passage를 캐오는 함수이다. | ||
| 108 | + */ | ||
| 109 | +const getHtmlMain = ( main, keywordText, html, defaultURL, findSearchResult ) => { | ||
| 110 | + const $ = cheerio.load( html ); | ||
| 111 | + let result = []; | ||
| 112 | + $( main ).each( (i, elem ) => { | ||
| 113 | + let keywordCheck = keywordChecking( keywordText, $, elem ); | ||
| 114 | + if( keywordCheck ) { | ||
| 115 | + let searchResult = {}; | ||
| 116 | + findSearchResult( searchResult, $, elem , defaultURL ); | ||
| 117 | + searchToResult( searchResult, result, keywordCheck ); | ||
| 118 | + } | ||
| 119 | + }); | ||
| 120 | + return result; | ||
| 121 | +} | ||
| 122 | + | ||
| 123 | +const search = {}; | ||
| 124 | + | ||
| 125 | +/** | ||
| 126 | + * @param {string} keywordText 검색할 내용 | ||
| 127 | + * @returns {{url:string,title:string,passage:string}[]} object 여러 개를 가진 list를 준다. 각 json의 url, title, passage로 접근가능 | ||
| 128 | + * @description 네이버에서 키워드의 내용을 크롤링해온다. | ||
| 129 | + */ | ||
| 130 | +search.naver = ( keywordText ) => { | ||
| 131 | + return new Promise( async ( resolve, reject ) => { | ||
| 132 | + let naverMain = "#main_pack strong", | ||
| 133 | + result = [], | ||
| 134 | + naverURL = searchURL.naver + "query=" + encodeURI( keywordText ); | ||
| 135 | + rp( { | ||
| 136 | + "uri" : naverURL, | ||
| 137 | + } ) | ||
| 138 | + .then( ( html ) => { | ||
| 139 | + result = getHtmlMain( naverMain, keywordText, html, naverURL, naver ); | ||
| 140 | + resolve( result ); | ||
| 141 | + }) | ||
| 142 | + .catch( ( err ) => { | ||
| 143 | + throw new Error( err ); | ||
| 144 | + }); | ||
| 145 | + }) | ||
| 146 | +} | ||
| 147 | + | ||
| 148 | +/** | ||
| 149 | + * @param {string} keywordText 검색할 내용 | ||
| 150 | + * @returns {{url:string,title:string,passage:string}[]} object 여러 개를 가진 list를 준다. 각 json의 url, title, passage로 접근가능 | ||
| 151 | + * @description 구글에서 키워드의 내용을 크롤링해온다. | ||
| 152 | + */ | ||
| 153 | +search.google = ( keywordText ) => { | ||
| 154 | + return new Promise( ( resolve, reject ) => { | ||
| 155 | + let googleMain = "#main a", | ||
| 156 | + result = [], | ||
| 157 | + googleURL = searchURL.google + "q=" + encodeURI( keywordText ) | ||
| 158 | + | ||
| 159 | + rp( { | ||
| 160 | + "uri" : googleURL, | ||
| 161 | + }) | ||
| 162 | + .then( ( html ) => { | ||
| 163 | + result = getHtmlMain( googleMain, keywordText, html, googleURL, google ); | ||
| 164 | + resolve( result ); | ||
| 165 | + }) | ||
| 166 | + .catch( ( err ) => { | ||
| 167 | + throw new Error( err ); | ||
| 168 | + }); | ||
| 169 | + }) | ||
| 170 | +} | ||
| 171 | + | ||
| 172 | +module.exports = search; | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
-
Please register or login to post a comment