├── README.md ├── google200.py ├── static ├── favicon.ico ├── g_logo.png ├── nav_logo.png ├── search-white.png └── style.css └── templates ├── index.html ├── result.html └── status.html /README.md: -------------------------------------------------------------------------------- 1 | google200 2 | ========= 3 | 4 | 基于google custom search api的google搜索镜像 5 | 6 | 部署步骤:
7 | 1. 安装flask, redis, requests等
8 | 2. 去console.developer.google.com申请google custom search api的key
9 | 3. python google200.py运行(你也可以安装gunicorn来启动)
10 | 4. demo: https://www.google200.com 11 | -------------------------------------------------------------------------------- /google200.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: xiangyu.ye 3 | # 77963306@qq.com 4 | # http://www.weibo.com/zjuyxy 5 | 6 | from flask import Flask, render_template, request, send_from_directory 7 | import os 8 | import redis 9 | import json 10 | import urllib 11 | import requests 12 | app = Flask(__name__) 13 | r = redis.Redis(host='127.0.0.1') 14 | 15 | proxies = { 16 | "http": "http://127.0.0.1:8087", 17 | "https": "http://127.0.0.1:8087", 18 | } 19 | 20 | api_url = 'https://www.googleapis.com/customsearch/v1' 21 | 22 | 23 | def get_search(start, query, proxy): 24 | keys_count = r.llen('keys') 25 | search_count = r.incr('google200search') 26 | key = r.lindex('keys', search_count % keys_count) 27 | query = urllib.quote(query.encode('utf-8')) 28 | url = api_url + '?hl=zh-cn&cx=007216634332860726138:rwhpajgfuxs&key=' + \ 29 | key + '&q=' + query + '&num=10' 30 | if int(start) > 0: 31 | url = url + '&start=' + str(start) 32 | if int(proxy) > 0: 33 | return requests.get(url, proxies=proxies, verify=False, timeout=2).text 34 | else: 35 | return requests.get(url, timeout=2).text 36 | 37 | 38 | @app.route('/static/') 39 | def static_file(sfile=None): 40 | return send_from_directory(os.path.join(app.root_path, 'static'), sfile) 41 | 42 | 43 | @app.route('/status') 44 | def status(): 45 | count = request.args.get('c', '0') 46 | rcount = request.args.get('r', '0') 47 | queries = r.zrevrange('querys', 0, int(count), withscores=True) 48 | vos = [] 49 | recently = r.lrange("recently", 0, rcount) 50 | for t in recently: 51 | vos.append((t.decode('utf-8'), '-')) 52 | for t in queries: 53 | vos.append((t[0].decode('utf-8'), int(t[1]))) 54 | return render_template('status.html', queries=vos) 55 | 56 | 57 | @app.route('/search') 58 | def search(): 59 | q = request.args.get('q', '') 60 | # save recently query words 61 | r.lpush("recently", q) 62 | if (r.llen("recently") > 200): 63 | r.rpop("recently") 64 | 65 | p = request.args.get('p', '0') 66 | words = q.split() 67 | words.sort() 68 | start = int(request.args.get('start', '0')) 69 | start = min(start, 90) 70 | key = '-'.join(words) + '-' + str(start) 71 | 72 | rvalue = r.get(key) 73 | 74 | j = None 75 | 76 | search = True 77 | if rvalue is not None: 78 | try: 79 | j = json.loads(rvalue) 80 | if 'items' in j: 81 | search = False 82 | except: 83 | j = None 84 | 85 | if search: 86 | rvalue = get_search(start, q, p) 87 | try: 88 | j = json.loads(rvalue) 89 | if 'searchInformation' in j: 90 | r.setex(key, rvalue, 172800) 91 | else: 92 | return render_template('index.html') 93 | except: 94 | print 'error_query', q 95 | return render_template('index.html') 96 | 97 | total_count = int(j['searchInformation']['totalResults']) 98 | start = start + 10 99 | cur_page = start / 10 100 | total_page = (total_count + 9) / 10 101 | total_page = min(total_page, 10) 102 | total_page = max(3, total_page) 103 | pre_page = max(cur_page - 1, 1) 104 | next_page = cur_page + 1 if cur_page < total_page else total_page 105 | first_page = 1 if cur_page <= 5 else cur_page - 5 106 | last_page = first_page + 9 107 | if (last_page > total_page): 108 | last_page = total_page 109 | first_page = max(1, total_page - 9) 110 | r.zincrby('querys', '-'.join(words)) 111 | 112 | search_time = j['searchInformation']['formattedSearchTime'] 113 | items = j['items'] if 'items' in j else [] 114 | total_count = j['searchInformation'][ 115 | 'formattedTotalResults'] 116 | 117 | return render_template('result.html', q=q, p=p, 118 | search_time=search_time, 119 | total_count=total_count, 120 | items=items, 121 | first_page=first_page, 122 | last_page=last_page, 123 | cur_page=cur_page, 124 | pre_page=pre_page, 125 | next_page=next_page) 126 | 127 | 128 | @app.route('/') 129 | def index(): 130 | return render_template('index.html', use_proxy=0) 131 | 132 | if __name__ == '__main__': 133 | app.run(host='0.0.0.0', port=5000, debug=True) 134 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zjuyxy/google200/e6ccfe08abbc7aa453a6ff351f989fb7574b5828/static/favicon.ico -------------------------------------------------------------------------------- /static/g_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zjuyxy/google200/e6ccfe08abbc7aa453a6ff351f989fb7574b5828/static/g_logo.png -------------------------------------------------------------------------------- /static/nav_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zjuyxy/google200/e6ccfe08abbc7aa453a6ff351f989fb7574b5828/static/nav_logo.png -------------------------------------------------------------------------------- /static/search-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zjuyxy/google200/e6ccfe08abbc7aa453a6ff351f989fb7574b5828/static/search-white.png -------------------------------------------------------------------------------- /static/style.css: -------------------------------------------------------------------------------- 1 | body { margin:0; padding:0; } 2 | img {border:0;} 3 | 4 | .wrapper{ 5 | min-height: 100%; 6 | position: relative; 7 | } 8 | 9 | .search-box{ 10 | position: relative; 11 | margin:170px auto 0; 12 | width: 640px; 13 | padding-right: 10px; 14 | } 15 | .search-box .glogo{ 16 | background: url("/static/g_logo.png") no-repeat; 17 | height: 95px; 18 | width: 269px; 19 | margin: 0 auto; 20 | } 21 | .search-box .s_box { 22 | padding: 50px 35px; 23 | height: 50px; 24 | width: 640px; 25 | } 26 | .s_text{ 27 | float: left; 28 | font-size: 15px; 29 | width: 520px; 30 | background-color: #FFFFFF; 31 | border-color: #C0C0C0 #D9D9D9 #D9D9D9; 32 | border-style: solid; 33 | border-width: 1px; 34 | color: #333333; 35 | display: inline-block; 36 | height: 28px; 37 | line-height: 13px; 38 | line-height: 28px\9; 39 | *+line-height: 28px; 40 | padding-left: 8px; 41 | vertical-align: bottom; 42 | -moz-border-bottom-colors: none; 43 | -moz-border-image: none; 44 | -moz-border-left-colors: none; 45 | -moz-border-right-colors: none; 46 | -moz-border-top-colors: none; 47 | } 48 | .s_text:hover { 49 | -moz-border-bottom-colors: none; 50 | -moz-border-image: none; 51 | -moz-border-left-colors: none; 52 | -moz-border-right-colors: none; 53 | -moz-border-top-colors: none; 54 | border-color: #A0A0A0 #B9B9B9 #B9B9B9; 55 | border-style: solid; 56 | border-width: 1px; 57 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1) inset; 58 | } 59 | .s_text:focus { 60 | /*border: 1px solid #4D90FE; 61 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3) inset;*/ 62 | outline: medium none; 63 | } 64 | 65 | 66 | 67 | .m-button { 68 | display: inline-block; 69 | width: 85px; 70 | text-align: center; 71 | vertical-align: middle; 72 | font-size: 13px; 73 | font-weight: bold; 74 | height: 30px; 75 | padding: 0 8px; 76 | margin: 0; 77 | line-height: 30px; 78 | -webkit-border-radius: 2px; 79 | -moz-border-radius: 2px; 80 | border-radius: 2px; 81 | -webkit-transition: all 0.218s; 82 | -moz-transition: all 0.218s; 83 | -ms-transition: all 0.218s; 84 | -o-transition: all 0.218s; 85 | transition: all 0.218s; 86 | border: 1px solid rgba(0,0,0,0.1); 87 | -webkit-user-select: none; 88 | -moz-user-select: none; 89 | user-select: none; 90 | cursor: default; 91 | margin-right: 10px; 92 | border: 1px solid #3079ed; 93 | color: #fff; 94 | text-shadow: 0 1px rgba(0,0,0,0.1); 95 | background-color: #4d90fe; 96 | background-image: -webkit-gradient(linear,left top,left bottom,from(#4d90fe),to(#4787ed)); /* blue */ 97 | background-image: -webkit-linear-gradient(top,#4d90fe,#4787ed); 98 | background-image: -moz-linear-gradient(top,#4d90fe,#4787ed); 99 | background-image: -ms-linear-gradient(top,#4d90fe,#4787ed); 100 | background-image: -o-linear-gradient(top,#4d90fe,#4787ed); 101 | background-image: linear-gradient(top,#4d90fe,#4787ed); 102 | } 103 | .m-button:hover { 104 | text-decoration: none; 105 | -webkit-transition: all 0.0s; 106 | -moz-transition: all 0.0s; 107 | -ms-transition: all 0.0s; 108 | -o-transition: all 0.0s; 109 | transition: all 0.0s; 110 | -webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.1); 111 | -moz-box-shadow: 0 1px 1px rgba(0,0,0,0.1); 112 | box-shadow: 0 1px 1px rgba(0,0,0,0.1); 113 | border: 1px solid #2f5bb7; 114 | color: #fff; 115 | text-shadow: 0 1px rgba(0,0,0,0.3); 116 | background-color: #357ae8; 117 | background-image: -webkit-gradient(linear,left top,left bottom,from(#4d90fe),to(#357ae8)); 118 | background-image: -webkit-linear-gradient(top,#4d90fe,#357ae8); 119 | background-image: -moz-linear-gradient(top,#4d90fe,#357ae8); 120 | background-image: -ms-linear-gradient(top,#4d90fe,#357ae8); 121 | background-image: -o-linear-gradient(top,#4d90fe,#357ae8); 122 | background-image: linear-gradient(top,#4d90fe,#357ae8); 123 | } 124 | .m-button:active { 125 | background-image: -webkit-linear-gradient(top,#f6f6f6,#f1f1f1); 126 | background-image: -moz-linear-gradient(top,#f6f6f6,#f1f1f1); 127 | background-image: -ms-linear-gradient(top,#f6f6f6,#f1f1f1); 128 | background-image: -o-linear-gradient(top,#f6f6f6,#f1f1f1); 129 | background-image: linear-gradient(top,#f6f6f6,#f1f1f1); 130 | -webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); 131 | -moz-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); 132 | box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); 133 | } 134 | 135 | .s_submit, 136 | .s_submit:hover { 137 | margin-left: 10px; 138 | width: 80px; 139 | background-image: url("/static/search-white.png"); 140 | _background-image: url("/static/search-white.jpg"); 141 | background-repeat: no-repeat; 142 | background-position: 29px 5px; 143 | } 144 | 145 | .search-result{ 146 | } 147 | .header-bar{ 148 | position: relative; 149 | background: none repeat scroll 0 0 #f1f1f1;; 150 | background-image: -webkit-gradient(radial,100 36,0,100 -40,120,from(#fafafa),to(#f1f1f1)); 151 | border-bottom: 1px solid #e5e5e5; 152 | height: 56px; 153 | padding-top:17px; 154 | /*padding-bottom:17px;*/ 155 | } 156 | .logo{ 157 | display:inline-block; 158 | padding-left:16px; 159 | padding-right:12px; 160 | float: left; 161 | } 162 | .logo a{ 163 | display: block; 164 | height: 37px; 165 | margin: 0; 166 | overflow: hidden; 167 | position: relative; 168 | width: 95px; 169 | } 170 | .logo img{ 171 | left: 0; 172 | position: absolute; 173 | top: -41px; 174 | } 175 | .form_box{ 176 | display:inline-block; 177 | margin-top: 5px; 178 | } 179 | .form_box .s_text{ 180 | padding: 4px 6px; 181 | border-spacing: 2px; 182 | color: #222; 183 | height:20px; 184 | width:529px; 185 | font: 17px arial,sans-serif; 186 | } 187 | 188 | .form_box .s_text:hover{ 189 | box-shadow: none; 190 | -webkit-box-shadow: none; 191 | -moz-box-shadow: none; 192 | border:1px solid gray; 193 | } 194 | .form_box .s_text:active, 195 | .form_box .s_text:focus{ 196 | -webkit-box-shadow: none; 197 | -moz-box-shadow: none; 198 | box-shadow: none; 199 | border: 1px solid #4D90FE; 200 | } 201 | .form_box .s_submit{ 202 | margin-left: 16px; 203 | width:72px; 204 | background-position: 25px 5px; 205 | } 206 | 207 | .content{ 208 | } 209 | .content .content_header{ 210 | height:40px; 211 | border-bottom:1px solid #ebebeb; 212 | line-height: 35px; 213 | } 214 | .link{ 215 | padding-left:120px; 216 | } 217 | .content_header a{ 218 | display: inline-block; 219 | font-size: small; 220 | height: 35px; 221 | margin: 2px 3px 0; 222 | padding: 0 8px; 223 | text-decoration:none; 224 | color:#777; 225 | } 226 | .content_header a:hover{ 227 | color:#555; 228 | } 229 | .content_header a.active, 230 | .content_header a.active:hover{ 231 | border-bottom:3px solid #dd4b39; 232 | font-weight:bold; 233 | color:#dd4b39; 234 | } 235 | .result_stats{ 236 | color:#808080; 237 | margin-left:132px; 238 | font-size:small; 239 | line-height: 35px; 240 | margin-bottom:10px; 241 | margin-top: 5px; 242 | } 243 | 244 | .search_result{ 245 | margin-top:10px; 246 | margin-left:135px; 247 | font-family: arial,sans-serif; 248 | width:600px; 249 | } 250 | .notice{ 251 | float:right; 252 | background-color: #ffffd9; 253 | border:1px solid #f3f3f3; 254 | color:#333; 255 | width:400px; 256 | height:150px; 257 | padding:10px; 258 | font-size:13px; 259 | margin-right:10px; 260 | line-height:20px; 261 | } 262 | .hidden{ 263 | display:none; 264 | } 265 | .notice h1{ 266 | font-family: arial,sans-serif; 267 | } 268 | .notice a{ 269 | color:#1a0dab; 270 | 271 | } 272 | .search_result .s_r{ 273 | margin-bottom:23px; 274 | } 275 | .s_r .title{ 276 | color: #1A0DAB; 277 | font-size: 18px; 278 | text-decoration: none; 279 | } 280 | .s_r .title:hover{ 281 | text-decoration: underline; 282 | } 283 | .s_r .title b{ 284 | color:#dd4b39; 285 | font-weight:normal; 286 | } 287 | .s_r .visible_url{ 288 | font-size: 13px; 289 | color:#006621; 290 | margin-bottom: 2px; 291 | } 292 | .s_r .min_content{ 293 | line-height: 1.4; 294 | word-wrap: break-word; 295 | color: #545454; 296 | font-size:13px; 297 | } 298 | .s_r .min_content b{ 299 | color: #dd4b39; 300 | font-style: normal; 301 | font-weight: normal; 302 | } 303 | 304 | .navcnt{ 305 | border-top:1px solid #ededed; 306 | margin-left: 138px; 307 | width: 600px; 308 | font-family: arial,sans-serif; 309 | margin-bottom: 50px; 310 | } 311 | .tr{ 312 | 313 | } 314 | .td{ 315 | text-align: center; 316 | } 317 | a.cur{ 318 | font-weight: bold; 319 | color:#222; 320 | } 321 | a.cur:hover{ 322 | text-decoration:none; 323 | } 324 | .csb{ 325 | height: 40px; 326 | display: block; 327 | overflow: hidden; 328 | white-space: nowrap; 329 | color: #1a0dab; 330 | } 331 | .fl{ 332 | text-decoration: none; 333 | display: block; 334 | color: #1a0dab; 335 | font-size: small; 336 | text-align: center; 337 | } 338 | .fl:hover{ 339 | text-decoration: underline; 340 | } 341 | .pn{ 342 | color:#1a0dab; /* #609 */ 343 | font-size:small; 344 | font-weight:bold; 345 | } 346 | .pn .txt:hover{ 347 | text-decoration:underline; 348 | } 349 | 350 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Google 9 | 10 | 11 | 12 |
13 | 22 |
23 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /templates/result.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Google 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 |
17 |
18 | 21 |
22 |
23 | 24 | 25 | 26 |
27 |
28 |
29 | 30 | 31 |
32 | 37 | 38 |
39 | {% autoescape false %} 40 | {% for item in items %} 41 | 42 |
43 | {{item['htmlTitle']}} 44 |
{{item['htmlFormattedUrl']}}
45 |
{{item['htmlSnippet']}}
46 |
47 | 48 | 49 | {% endfor %} 50 | {% endautoescape %} 51 | 52 | 53 | 54 | 55 |
56 | {% if items|length> 0 %} 57 | 92 | {% endif %} 93 |
94 | 95 | 96 | 97 |
98 |
99 | 100 | -------------------------------------------------------------------------------- /templates/status.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Google 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | {% autoescape false %} 18 | {% for query in queries %} 19 |
20 |
{{query[0]}},{{query[1]}}
21 |
22 | {% endfor %} 23 | {% endautoescape %} 24 |
25 |
26 |
27 | 28 | --------------------------------------------------------------------------------