├── .gitignore
├── GUI.png
├── LICENSE
├── README.md
├── README_zh.md
├── __init__.py
├── about.ui
├── crawler.py
├── downloader.py
├── example_list.txt
├── image_downloader.py
├── image_downloader_gui.py
├── image_downloader_gui.spec
├── logger.py
├── mainwindow.py
├── mainwindow.ui
├── requirements.txt
├── ui_about.py
├── ui_mainwindow.py
└── utils.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | images
3 | .idea
4 | *ghostdriver.log
5 | .vscode
6 | download_images
7 | build
8 | dist
--------------------------------------------------------------------------------
/GUI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QianyanTech/Image-Downloader/2ff2ce0e5cf4079565fb1b96682a54809b25970d/GUI.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Yabin Zheng
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Image Downloader
2 |
3 | [](https://996.icu)
4 |
5 | ## [中文说明](https://github.com/QianyanTech/Image-Downloader/blob/master/README_zh.md)
6 |
7 | ## 1. Introdoction
8 |
9 | Crawl and download images using Selenium or API
10 | Using python3 and PyQt5
11 |
12 | ## 2. Key features
13 |
14 | + Supported Search Engine: Google, Bing, Baidu
15 | + Keywords input from keyboard, or input from line seperated keywords list file for batch process.
16 | + Download image using customizable number of threads.
17 | + Fully supported conditional search (eg. filetype:, site:).
18 | + Switch for Google safe mode.
19 | + Proxy configuration (socks, http).
20 | + CMD and GUI ways of using are provided.
21 |
22 | ## 3. Usage
23 |
24 | ### 3.1 GUI
25 |
26 | Run `image_downloader_gui.py` script to yank GUI:
27 | ```bash
28 | python image_downloader_gui.py
29 | ```
30 |
31 | 
32 |
33 | ### 3.2 CMD
34 |
35 | ```bash
36 | usage: image_downloader.py [-h] [--engine {Google,Bing,Baidu}]
37 | [--driver {chrome_headless,chrome,api}]
38 | [--max-number MAX_NUMBER]
39 | [--num-threads NUM_THREADS] [--timeout TIMEOUT]
40 | [--output OUTPUT] [--safe-mode] [--face-only]
41 | [--proxy_http PROXY_HTTP]
42 | [--proxy_socks5 PROXY_SOCKS5]
43 | keywords
44 | ```
45 |
46 | ## Star History
47 |
48 | [](https://star-history.com/#QianyanTech/Image-Downloader&Date)
49 |
50 | ## License
51 |
52 | + MIT License
53 | + 996ICU License
54 |
--------------------------------------------------------------------------------
/README_zh.md:
--------------------------------------------------------------------------------
1 | # Image Downloader
2 |
3 | [](https://996.icu)
4 |
5 | ## 1. 简介
6 |
7 | + 从图片搜索引擎,爬取关键字搜索的原图URL并下载
8 | + 开发语言python,采用Requests、Selenium等库进行开发
9 |
10 | ## 2. 功能
11 |
12 | + 支持的搜索引擎: Google, 必应, 百度
13 | + 提供GUI及CMD版本
14 | + GUI版本支持关键词键入,以及通过关键词列表文件(行分隔,**使用UTF-8编码**)输入进行批处理爬图下载
15 | + 可配置线程数进行并发下载,提高下载速度
16 | + 支持搜索引擎的条件查询(如 :site)
17 | + 支持socks5和http代理的配置,方便科学上网用户
18 |
19 | ## 3. 用法
20 |
21 | ### 3.1 图形界面
22 |
23 | 运行`image_downloader_gui.py`脚本以启动GUI界面
24 |
25 | ```bash
26 | python image_downloader_gui.py
27 | ```
28 |
29 | 
30 |
31 | ### 3.2 命令行
32 |
33 | ```bash
34 | usage: image_downloader.py [-h] [--engine {Google,Bing,Baidu}]
35 | [--driver {chrome_headless,chrome,api}]
36 | [--max-number MAX_NUMBER]
37 | [--num-threads NUM_THREADS] [--timeout TIMEOUT]
38 | [--output OUTPUT] [--safe-mode] [--face-only]
39 | [--proxy_http PROXY_HTTP]
40 | [--proxy_socks5 PROXY_SOCKS5]
41 | keywords
42 | ```
43 |
44 | ## 许可
45 |
46 | + MIT License
47 | + 996ICU License
48 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QianyanTech/Image-Downloader/2ff2ce0e5cf4079565fb1b96682a54809b25970d/__init__.py
--------------------------------------------------------------------------------
/about.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | Dialog_about
4 |
5 |
6 |
7 | 0
8 | 0
9 | 600
10 | 200
11 |
12 |
13 |
14 |
15 | 0
16 | 0
17 |
18 |
19 |
20 | Dialog
21 |
22 |
23 | -
24 |
25 |
26 | Qt::Vertical
27 |
28 |
29 |
30 | 20
31 | 43
32 |
33 |
34 |
35 |
36 | -
37 |
38 |
39 |
40 | 10
41 |
42 |
43 |
44 | Author:
45 |
46 |
47 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
48 |
49 |
50 |
51 | -
52 |
53 |
54 |
55 | 0
56 | 0
57 |
58 |
59 |
60 |
61 | 10
62 |
63 |
64 |
65 | Qt::NoFocus
66 |
67 |
68 | Yabin Zheng
69 |
70 |
71 | 0
72 |
73 |
74 | true
75 |
76 |
77 |
78 | -
79 |
80 |
81 | Qt::Horizontal
82 |
83 |
84 |
85 | 34
86 | 20
87 |
88 |
89 |
90 |
91 | -
92 |
93 |
94 |
95 | 10
96 |
97 |
98 |
99 | Email:
100 |
101 |
102 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
103 |
104 |
105 |
106 | -
107 |
108 |
109 |
110 | 0
111 | 0
112 |
113 |
114 |
115 |
116 | 10
117 |
118 |
119 |
120 | Qt::NoFocus
121 |
122 |
123 | sczhengyabin@hotmail.com
124 |
125 |
126 | 0
127 |
128 |
129 | true
130 |
131 |
132 |
133 | -
134 |
135 |
136 | Qt::Horizontal
137 |
138 |
139 |
140 | 34
141 | 20
142 |
143 |
144 |
145 |
146 | -
147 |
148 |
149 |
150 | 10
151 |
152 |
153 |
154 | Project Home:
155 |
156 |
157 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
158 |
159 |
160 |
161 | -
162 |
163 |
164 |
165 | 0
166 | 0
167 |
168 |
169 |
170 |
171 | 10
172 |
173 |
174 |
175 | Qt::NoFocus
176 |
177 |
178 | https://github.com/sczhengyabin/Google-Image-Downloader
179 |
180 |
181 | 0
182 |
183 |
184 | true
185 |
186 |
187 |
188 | -
189 |
190 |
191 | Qt::Vertical
192 |
193 |
194 |
195 | 20
196 | 42
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
--------------------------------------------------------------------------------
/crawler.py:
--------------------------------------------------------------------------------
1 | """ Crawl image urls from image search engine. """
2 | # -*- coding: utf-8 -*-
3 | # author: Yabin Zheng
4 | # Email: sczhengyabin@hotmail.com
5 |
6 | from __future__ import print_function
7 |
8 | import re
9 | import time
10 | import sys
11 | import os
12 | import json
13 | import shutil
14 |
15 | from urllib.parse import unquote, quote
16 | from selenium import webdriver
17 | from selenium.webdriver.common.by import By
18 | import requests
19 | from concurrent import futures
20 |
21 | g_headers = {
22 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
23 | "Proxy-Connection": "keep-alive",
24 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
25 | "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36",
26 | "Accept-Encoding": "gzip, deflate, sdch",
27 | # 'Connection': 'close',
28 | }
29 |
30 | if getattr(sys, 'frozen', False):
31 | bundle_dir = sys._MEIPASS
32 | else:
33 | bundle_dir = os.path.dirname(os.path.abspath(__file__))
34 |
35 |
36 | def my_print(msg, quiet=False):
37 | if not quiet:
38 | print(msg)
39 |
40 |
41 | def google_gen_query_url(keywords, face_only=False, safe_mode=False, image_type=None, color=None):
42 | base_url = "https://www.google.com/search?tbm=isch&hl=en"
43 | keywords_str = "&q=" + quote(keywords)
44 | query_url = base_url + keywords_str
45 |
46 | if safe_mode is True:
47 | query_url += "&safe=on"
48 | else:
49 | query_url += "&safe=off"
50 |
51 | filter_url = "&tbs="
52 |
53 | if color is not None:
54 | if color == "bw":
55 | filter_url += "ic:gray%2C"
56 | else:
57 | filter_url += "ic:specific%2Cisc:{}%2C".format(color.lower())
58 |
59 | if image_type is not None:
60 | if image_type.lower() == "linedrawing":
61 | image_type = "lineart"
62 | filter_url += "itp:{}".format(image_type)
63 |
64 | if face_only is True:
65 | filter_url += "itp:face"
66 |
67 | query_url += filter_url
68 | return query_url
69 |
70 |
71 | def google_image_url_from_webpage(driver, max_number, quiet=False):
72 | thumb_elements_old = []
73 | thumb_elements = []
74 | while True:
75 | try:
76 | thumb_elements = driver.find_elements(By.CLASS_NAME, "rg_i")
77 | my_print("Find {} images.".format(len(thumb_elements)), quiet)
78 | if len(thumb_elements) >= max_number:
79 | break
80 | if len(thumb_elements) == len(thumb_elements_old):
81 | break
82 | thumb_elements_old = thumb_elements
83 | driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
84 | time.sleep(2)
85 | show_more = driver.find_elements(By.CLASS_NAME, "mye4qd")
86 | if len(show_more) == 1 and show_more[0].is_displayed() and show_more[0].is_enabled():
87 | my_print("Click show_more button.", quiet)
88 | show_more[0].click()
89 | time.sleep(3)
90 | except Exception as e:
91 | print("Exception ", e)
92 | pass
93 |
94 | if len(thumb_elements) == 0:
95 | return []
96 |
97 | my_print("Click on each thumbnail image to get image url, may take a moment ...", quiet)
98 |
99 | retry_click = []
100 | for i, elem in enumerate(thumb_elements):
101 | try:
102 | if i != 0 and i % 50 == 0:
103 | my_print("{} thumbnail clicked.".format(i), quiet)
104 | if not elem.is_displayed() or not elem.is_enabled():
105 | retry_click.append(elem)
106 | continue
107 | elem.click()
108 | except Exception as e:
109 | print("Error while clicking in thumbnail:", e)
110 | retry_click.append(elem)
111 |
112 | if len(retry_click) > 0:
113 | my_print("Retry some failed clicks ...", quiet)
114 | for elem in retry_click:
115 | try:
116 | if elem.is_displayed() and elem.is_enabled():
117 | elem.click()
118 | except Exception as e:
119 | print("Error while retrying click:", e)
120 |
121 | image_elements = driver.find_elements(By.CLASS_NAME, "islib")
122 | image_urls = list()
123 | url_pattern = r"imgurl=\S*&imgrefurl"
124 |
125 | for image_element in image_elements[:max_number]:
126 | outer_html = image_element.get_attribute("outerHTML")
127 | re_group = re.search(url_pattern, outer_html)
128 | if re_group is not None:
129 | image_url = unquote(re_group.group()[7:-14])
130 | image_urls.append(image_url)
131 | return image_urls
132 |
133 |
134 | def bing_gen_query_url(keywords, face_only=False, safe_mode=False, image_type=None, color=None):
135 | base_url = "https://www.bing.com/images/search?"
136 | keywords_str = "&q=" + quote(keywords)
137 | query_url = base_url + keywords_str
138 | filter_url = "&qft="
139 | if face_only is True:
140 | filter_url += "+filterui:face-face"
141 |
142 | if image_type is not None:
143 | filter_url += "+filterui:photo-{}".format(image_type)
144 |
145 | if color is not None:
146 | if color == "bw" or color == "color":
147 | filter_url += "+filterui:color2-{}".format(color.lower())
148 | else:
149 | filter_url += "+filterui:color2-FGcls_{}".format(color.upper())
150 |
151 | query_url += filter_url
152 |
153 | return query_url
154 |
155 |
156 | def bing_image_url_from_webpage(driver):
157 | image_urls = list()
158 |
159 | time.sleep(10)
160 | img_count = 0
161 |
162 | while True:
163 | image_elements = driver.find_elements(By.CLASS_NAME, "iusc")
164 | if len(image_elements) > img_count:
165 | img_count = len(image_elements)
166 | driver.execute_script(
167 | "window.scrollTo(0, document.body.scrollHeight);")
168 | else:
169 | smb = driver.find_elements(By.CLASS_NAME, "btn_seemore")
170 | if len(smb) > 0 and smb[0].is_displayed():
171 | smb[0].click()
172 | else:
173 | break
174 | time.sleep(3)
175 | for image_element in image_elements:
176 | m_json_str = image_element.get_attribute("m")
177 | m_json = json.loads(m_json_str)
178 | image_urls.append(m_json["murl"])
179 | return image_urls
180 |
181 | def bing_get_image_url_using_api(keywords, max_number=10000, face_only=False,
182 | proxy=None, proxy_type=None):
183 | proxies = None
184 | if proxy and proxy_type:
185 | proxies = {"http": "{}://{}".format(proxy_type, proxy),
186 | "https": "{}://{}".format(proxy_type, proxy)}
187 | start = 1
188 | image_urls = []
189 | while start <= max_number:
190 | url = 'https://www.bing.com/images/async?q={}&first={}&count=35'.format(keywords, start)
191 | res = requests.get(url, proxies=proxies, headers=g_headers)
192 | res.encoding = "utf-8"
193 | image_urls_batch = re.findall('murl":"(.*?)"', res.text)
194 | if len(image_urls) > 0 and image_urls_batch[-1] == image_urls[-1]:
195 | break
196 | image_urls += image_urls_batch
197 | start += len(image_urls_batch)
198 | return image_urls
199 |
200 | baidu_color_code = {
201 | "white": 1024, "bw": 2048, "black": 512, "pink": 64, "blue": 16, "red": 1,
202 | "yellow": 2, "purple": 32, "green": 4, "teal": 8, "orange": 256, "brown": 128
203 | }
204 |
205 | def baidu_gen_query_url(keywords, face_only=False, safe_mode=False, color=None):
206 | base_url = "https://image.baidu.com/search/index?tn=baiduimage"
207 | keywords_str = "&word=" + quote(keywords)
208 | query_url = base_url + keywords_str
209 | if face_only is True:
210 | query_url += "&face=1"
211 | if color is not None:
212 | print(color, baidu_color_code[color.lower()])
213 | if color is not None:
214 | query_url += "&ic={}".format(baidu_color_code[color.lower()])
215 | print(query_url)
216 | return query_url
217 |
218 |
219 | def baidu_image_url_from_webpage(driver):
220 | time.sleep(10)
221 | image_elements = driver.find_elements(By.CLASS_NAME, "imgitem")
222 | image_urls = list()
223 |
224 | for image_element in image_elements:
225 | image_url = image_element.get_attribute("data-objurl")
226 | image_urls.append(image_url)
227 | return image_urls
228 |
229 |
230 | def baidu_get_image_url_using_api(keywords, max_number=10000, face_only=False,
231 | proxy=None, proxy_type=None):
232 | def decode_url(url):
233 | in_table = '0123456789abcdefghijklmnopqrstuvw'
234 | out_table = '7dgjmoru140852vsnkheb963wtqplifca'
235 | translate_table = str.maketrans(in_table, out_table)
236 | mapping = {'_z2C$q': ':', '_z&e3B': '.', 'AzdH3F': '/'}
237 | for k, v in mapping.items():
238 | url = url.replace(k, v)
239 | return url.translate(translate_table)
240 |
241 | base_url = "https://image.baidu.com/search/acjson?tn=resultjson_com&ipn=rj&ct=201326592"\
242 | "&lm=7&fp=result&ie=utf-8&oe=utf-8&st=-1"
243 | keywords_str = "&word={}&queryWord={}".format(
244 | quote(keywords), quote(keywords))
245 | query_url = base_url + keywords_str
246 | query_url += "&face={}".format(1 if face_only else 0)
247 |
248 | init_url = query_url + "&pn=0&rn=30"
249 |
250 | proxies = None
251 | if proxy and proxy_type:
252 | proxies = {"http": "{}://{}".format(proxy_type, proxy),
253 | "https": "{}://{}".format(proxy_type, proxy)}
254 |
255 | res = requests.get(init_url, proxies=proxies, headers=g_headers)
256 | init_json = json.loads(res.text.replace(r"\'", "").encode("utf-8"), strict=False)
257 | total_num = init_json['listNum']
258 |
259 | target_num = min(max_number, total_num)
260 | crawl_num = min(target_num * 2, total_num)
261 |
262 | crawled_urls = list()
263 | batch_size = 30
264 |
265 | with futures.ThreadPoolExecutor(max_workers=5) as executor:
266 | future_list = list()
267 |
268 | def process_batch(batch_no, batch_size):
269 | image_urls = list()
270 | url = query_url + \
271 | "&pn={}&rn={}".format(batch_no * batch_size, batch_size)
272 | try_time = 0
273 | while True:
274 | try:
275 | response = requests.get(url, proxies=proxies, headers=g_headers)
276 | break
277 | except Exception as e:
278 | try_time += 1
279 | if try_time > 3:
280 | print(e)
281 | return image_urls
282 | response.encoding = 'utf-8'
283 | res_json = json.loads(response.text.replace(r"\'", ""), strict=False)
284 | for data in res_json['data']:
285 | # if 'middleURL' in data.keys():
286 | # url = data['middleURL']
287 | # image_urls.append(url)
288 | if 'objURL' in data.keys():
289 | url = unquote(decode_url(data['objURL']))
290 | if 'src=' in url:
291 | url_p1 = url.split('src=')[1]
292 | url = url_p1.split('&refer=')[0]
293 | image_urls.append(url)
294 | # print(url)
295 | elif 'replaceUrl' in data.keys() and len(data['replaceUrl']) == 2:
296 | image_urls.append(data['replaceUrl'][1]['ObjURL'])
297 |
298 | return image_urls
299 |
300 | for i in range(0, int((crawl_num + batch_size - 1) / batch_size)):
301 | future_list.append(executor.submit(process_batch, i, batch_size))
302 | for future in futures.as_completed(future_list):
303 | if future.exception() is None:
304 | crawled_urls += future.result()
305 | else:
306 | print(future.exception())
307 |
308 | return crawled_urls[:min(len(crawled_urls), target_num)]
309 |
310 |
311 | def crawl_image_urls(keywords, engine="Google", max_number=10000,
312 | face_only=False, safe_mode=False, proxy=None,
313 | proxy_type="http", quiet=False, browser="chrome_headless", image_type=None, color=None):
314 | """
315 | Scrape image urls of keywords from Google Image Search
316 | :param keywords: keywords you want to search
317 | :param engine: search engine used to search images
318 | :param max_number: limit the max number of image urls the function output, equal or less than 0 for unlimited
319 | :param face_only: image type set to face only, provided by Google
320 | :param safe_mode: switch for safe mode of Google Search
321 | :param proxy: proxy address, example: socks5 127.0.0.1:1080
322 | :param proxy_type: socks5, http
323 | :param browser: browser to use when crawl image urls
324 | :return: list of scraped image urls
325 | """
326 |
327 | my_print("\nScraping From {} Image Search ...\n".format(engine), quiet)
328 | my_print("Keywords: " + keywords, quiet)
329 | if max_number <= 0:
330 | my_print("Number: No limit", quiet)
331 | max_number = 10000
332 | else:
333 | my_print("Number: {}".format(max_number), quiet)
334 | my_print("Face Only: {}".format(str(face_only)), quiet)
335 | my_print("Safe Mode: {}".format(str(safe_mode)), quiet)
336 |
337 | if engine == "Google":
338 | query_url = google_gen_query_url(keywords, face_only, safe_mode, image_type, color)
339 | elif engine == "Bing":
340 | query_url = bing_gen_query_url(keywords, face_only, safe_mode, image_type, color)
341 | elif engine == "Baidu":
342 | query_url = baidu_gen_query_url(keywords, face_only, safe_mode, color)
343 | else:
344 | return
345 |
346 | my_print("Query URL: " + query_url, quiet)
347 |
348 | image_urls = []
349 |
350 | if browser != "api":
351 | browser = str.lower(browser)
352 | chrome_path = shutil.which("chromedriver")
353 | chrome_options = webdriver.ChromeOptions()
354 | if "headless" in browser:
355 | chrome_options.add_argument("headless")
356 | if proxy is not None and proxy_type is not None:
357 | chrome_options.add_argument("--proxy-server={}://{}".format(proxy_type, proxy))
358 | driver = webdriver.Chrome(chrome_path, chrome_options=chrome_options)
359 |
360 | if engine == "Google":
361 | driver.set_window_size(1920, 1080)
362 | driver.get(query_url)
363 | image_urls = google_image_url_from_webpage(driver, max_number, quiet)
364 | elif engine == "Bing":
365 | driver.set_window_size(1920, 1080)
366 | driver.get(query_url)
367 | image_urls = bing_image_url_from_webpage(driver)
368 | else: # Baidu
369 | driver.set_window_size(10000, 7500)
370 | driver.get(query_url)
371 | image_urls = baidu_image_url_from_webpage(driver)
372 | driver.close()
373 | else: # api
374 | if engine == "Baidu":
375 | image_urls = baidu_get_image_url_using_api(keywords, max_number=max_number, face_only=face_only,
376 | proxy=proxy, proxy_type=proxy_type)
377 | elif engine == "Bing":
378 | image_urls = bing_get_image_url_using_api(keywords, max_number=max_number, face_only=face_only,
379 | proxy=proxy, proxy_type=proxy_type)
380 | else:
381 | my_print("Engine {} is not supported on API mode.".format(engine))
382 |
383 | if max_number > len(image_urls):
384 | output_num = len(image_urls)
385 | else:
386 | output_num = max_number
387 |
388 | my_print("\n== {0} out of {1} crawled images urls will be used.\n".format(
389 | output_num, len(image_urls)), quiet)
390 |
391 | return image_urls[0:output_num]
392 |
--------------------------------------------------------------------------------
/downloader.py:
--------------------------------------------------------------------------------
1 | """ Download image according to given urls and automatically rename them in order. """
2 | # -*- coding: utf-8 -*-
3 | # author: Yabin Zheng
4 | # Email: sczhengyabin@hotmail.com
5 |
6 | from __future__ import print_function
7 |
8 | import shutil
9 | import imghdr
10 | import os
11 | import concurrent.futures
12 | import requests
13 | import socket
14 |
15 | headers = {
16 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
17 | "Proxy-Connection": "keep-alive",
18 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
19 | "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36",
20 | "Accept-Encoding": "gzip, deflate, sdch",
21 | # 'Connection': 'close',
22 | }
23 |
24 | def download_image(image_url, dst_dir, file_name, timeout=20, proxy_type=None, proxy=None):
25 | proxies = None
26 | if proxy_type is not None:
27 | proxies = {
28 | "http": proxy_type + "://" + proxy,
29 | "https": proxy_type + "://" + proxy
30 | }
31 |
32 | response = None
33 | file_path = os.path.join(dst_dir, file_name)
34 | try_times = 0
35 | while True:
36 | try:
37 | try_times += 1
38 | response = requests.get(
39 | image_url, headers=headers, timeout=timeout, proxies=proxies)
40 | with open(file_path, 'wb') as f:
41 | f.write(response.content)
42 | response.close()
43 | file_type = imghdr.what(file_path)
44 | # if file_type is not None:
45 | if file_type in ["jpg", "jpeg", "png", "bmp", "webp"]:
46 | new_file_name = "{}.{}".format(file_name, file_type)
47 | new_file_path = os.path.join(dst_dir, new_file_name)
48 | shutil.move(file_path, new_file_path)
49 | print("## OK: {} {}".format(new_file_name, image_url))
50 | else:
51 | os.remove(file_path)
52 | print("## Err: TYPE({}) {}".format(file_type, image_url))
53 | break
54 | except Exception as e:
55 | if try_times < 3:
56 | continue
57 | if response:
58 | response.close()
59 | print("## Fail: {} {}".format(image_url, e.args))
60 | break
61 |
62 |
63 | def download_images(image_urls, dst_dir, file_prefix="img", concurrency=50, timeout=20, proxy_type=None, proxy=None):
64 | """
65 | Download image according to given urls and automatically rename them in order.
66 | :param timeout:
67 | :param proxy:
68 | :param proxy_type:
69 | :param image_urls: list of image urls
70 | :param dst_dir: output the downloaded images to dst_dir
71 | :param file_prefix: if set to "img", files will be in format "img_xxx.jpg"
72 | :param concurrency: number of requests process simultaneously
73 | :return: none
74 | """
75 |
76 | socket.setdefaulttimeout(timeout)
77 |
78 | with concurrent.futures.ThreadPoolExecutor(max_workers=concurrency) as executor:
79 | future_list = list()
80 | count = 0
81 | if not os.path.exists(dst_dir):
82 | os.makedirs(dst_dir)
83 | for image_url in image_urls:
84 | file_name = file_prefix + "_" + "%04d" % count
85 | future_list.append(executor.submit(
86 | download_image, image_url, dst_dir, file_name, timeout, proxy_type, proxy))
87 | count += 1
88 | concurrent.futures.wait(future_list, timeout=180)
89 |
--------------------------------------------------------------------------------
/example_list.txt:
--------------------------------------------------------------------------------
1 | Micheal Jordan
2 | Yao Ming
3 | Kobe Bryant
4 | Lebron James
5 | Tracy McGrady
6 | Dwyane Wade
7 | Kevin Durant
8 | Stephen Curry
9 | 唐嫣
10 | 赵丽颖
11 | 郑爽
12 | 杨幂
13 | 范冰冰
14 | angelababy
15 | 井柏然
16 | 蒋劲夫
17 | 陈学冬
18 | 杨洋
19 | 鹿晗
20 | 胡歌
--------------------------------------------------------------------------------
/image_downloader.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # author: Yabin Zheng
3 | # Email: sczhengyabin@hotmail.com
4 |
5 | from __future__ import print_function
6 |
7 | import argparse
8 | import sys
9 |
10 | import crawler
11 | import downloader
12 | import utils
13 |
14 | def main(argv):
15 | parser = argparse.ArgumentParser(description="Image Downloader")
16 | parser.add_argument("keywords", type=str,
17 | help='Keywords to search. ("in quotes")')
18 | parser.add_argument("--engine", "-e", type=str, default="Google",
19 | help="Image search engine.", choices=["Google", "Bing", "Baidu"])
20 | parser.add_argument("--driver", "-d", type=str, default="chrome_headless",
21 | help="Image search engine.", choices=["chrome_headless", "chrome", "api"])
22 | parser.add_argument("--max-number", "-n", type=int, default=100,
23 | help="Max number of images download for the keywords.")
24 | parser.add_argument("--num-threads", "-j", type=int, default=50,
25 | help="Number of threads to concurrently download images.")
26 | parser.add_argument("--timeout", "-t", type=int, default=10,
27 | help="Seconds to timeout when download an image.")
28 | parser.add_argument("--output", "-o", type=str, default="./download_images",
29 | help="Output directory to save downloaded images.")
30 | parser.add_argument("--safe-mode", "-S", action="store_true", default=False,
31 | help="Turn on safe search mode. (Only effective in Google)")
32 | parser.add_argument("--face-only", "-F", action="store_true", default=False,
33 | help="Only search for ")
34 | parser.add_argument("--proxy_http", "-ph", type=str, default=None,
35 | help="Set http proxy (e.g. 192.168.0.2:8080)")
36 | parser.add_argument("--proxy_socks5", "-ps", type=str, default=None,
37 | help="Set socks5 proxy (e.g. 192.168.0.2:1080)")
38 | # type is not supported for Baidu
39 | parser.add_argument("--type", "-ty", type=str, default=None,
40 | help="What kinds of images to download.", choices=["clipart", "linedrawing", "photograph"])
41 | # Bing: color for colored images, bw for black&white images, other color contains Red, orange, yellow, green
42 | # Teal, Blue, Purple, Pink, Brown, Black, Gray, White
43 | # Baidu: white, bw, black, pink, blue, red, yellow, purple, green, teal, orange, brown
44 | # Google: bw, red, orange, yellow, green, teal, blue, purple, pink, white, gray, black, brown
45 | parser.add_argument("--color", "-cl", type=str, default=None,
46 | help="Specify the color of desired images.")
47 |
48 | args = parser.parse_args(args=argv)
49 |
50 | proxy_type = None
51 | proxy = None
52 | if args.proxy_http is not None:
53 | proxy_type = "http"
54 | proxy = args.proxy_http
55 | elif args.proxy_socks5 is not None:
56 | proxy_type = "socks5"
57 | proxy = args.proxy_socks5
58 |
59 | if not utils.resolve_dependencies(args.driver):
60 | print("Dependencies not resolved, exit.")
61 | return
62 |
63 | crawled_urls = crawler.crawl_image_urls(args.keywords,
64 | engine=args.engine, max_number=args.max_number,
65 | face_only=args.face_only, safe_mode=args.safe_mode,
66 | proxy_type=proxy_type, proxy=proxy,
67 | browser=args.driver, image_type=args.type, color=args.color)
68 | downloader.download_images(image_urls=crawled_urls, dst_dir=args.output,
69 | concurrency=args.num_threads, timeout=args.timeout,
70 | proxy_type=proxy_type, proxy=proxy,
71 | file_prefix=args.engine)
72 |
73 | print("Finished.")
74 |
75 |
76 | if __name__ == '__main__':
77 | main(sys.argv[1:])
78 |
--------------------------------------------------------------------------------
/image_downloader_gui.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # author: Yabin Zheng
3 | # Email: sczhengyabin@hotmail.com
4 |
5 | from __future__ import print_function
6 | import sys
7 | from mainwindow import MainWindow
8 | from PyQt5.Qt import QApplication
9 |
10 |
11 | def main():
12 | app = QApplication(sys.argv)
13 |
14 | font = app.font()
15 | if sys.platform.startswith("win"):
16 | font.setFamily("Microsoft YaHei")
17 | else:
18 | font.setFamily("Ubuntu")
19 | app.setFont(font)
20 |
21 | main_window = MainWindow()
22 | main_window.setWindowTitle("Image Downloader")
23 | main_window.show()
24 |
25 | sys.exit(app.exec_())
26 |
27 |
28 | if __name__ == '__main__':
29 | main()
30 |
--------------------------------------------------------------------------------
/image_downloader_gui.spec:
--------------------------------------------------------------------------------
1 | # -*- mode: python -*-
2 |
3 | block_cipher = None
4 |
5 |
6 | a = Analysis(['image_downloader_gui.py'],
7 | pathex=['./'],
8 | binaries=None,
9 | datas=[],
10 | hiddenimports=[],
11 | hookspath=[],
12 | runtime_hooks=[],
13 | excludes=[],
14 | win_no_prefer_redirects=False,
15 | win_private_assemblies=False,
16 | cipher=block_cipher)
17 | pyz = PYZ(a.pure, a.zipped_data,
18 | cipher=block_cipher)
19 | exe = EXE(pyz,
20 | a.scripts,
21 | a.binaries,
22 | a.zipfiles,
23 | a.datas,
24 | name='image_downloader_gui',
25 | debug=False,
26 | strip=False,
27 | upx=True,
28 | console=False )
29 |
--------------------------------------------------------------------------------
/logger.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # author: Yabin Zheng
3 | # Email: sczhengyabin@hotmail.com
4 |
5 | import sys
6 |
7 |
8 | class Logger(object):
9 | def __init__(self):
10 | self.log_hooks = []
11 | self.saved_stderr = sys.stderr
12 | sys.stdout = self
13 | sys.stderr = self
14 |
15 | def log(self, log_str):
16 | # self.saved_stderr.write(log_str)
17 | logs = log_str.splitlines()
18 | for a_log in logs:
19 | for log_hook in self.log_hooks:
20 | log_hook(a_log)
21 |
22 | def write(self, text):
23 | self.log(text)
24 |
25 | def flush(self):
26 | pass
27 |
28 |
29 | logger = Logger()
30 |
--------------------------------------------------------------------------------
/mainwindow.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # author: Yabin Zheng
3 | # Email: sczhengyabin@hotmail.com
4 |
5 | from ui_mainwindow import Ui_MainWindow
6 | from ui_about import Ui_Dialog_about
7 | import utils
8 |
9 | from PyQt5.Qt import *
10 | from PyQt5.QtTest import QTest
11 | from threading import Thread
12 | import shlex
13 | import os
14 |
15 | import image_downloader
16 | from logger import logger
17 |
18 |
19 | class DialogAbout(QDialog, Ui_Dialog_about):
20 | def __init__(self):
21 | QDialog.__init__(self)
22 | self.setupUi(self)
23 |
24 |
25 | class MainWindow(QMainWindow, Ui_MainWindow):
26 | def __init__(self):
27 | logger.log_hooks.append(self.log)
28 | self.log_queue = []
29 |
30 | QMainWindow.__init__(self)
31 |
32 | self.setupUi(self)
33 |
34 | self.dialog_about = DialogAbout()
35 |
36 | self.state = "stop"
37 |
38 | self.elapsed_timer = QElapsedTimer()
39 |
40 | self.update_timer = QTimer()
41 | self.update_timer.setInterval(100)
42 | self.update_timer.timeout.connect(self.update_elapsed_time)
43 |
44 | self.process_log_timer = QTimer()
45 | self.process_log_timer.setInterval(100)
46 | self.process_log_timer.timeout.connect(self.progress_log)
47 | self.process_log_timer.start()
48 |
49 | self.actionAbout.triggered.connect(self.dialog_about.show)
50 |
51 | self.pushButton_load_file.clicked.connect(
52 | lambda: self.lineEdit_path2file.setText(QFileDialog.getOpenFileName(
53 | self, "Load keywords from file", "./", "Text files (*.txt)")[0]))
54 | self.pushButton_output.clicked.connect(
55 | lambda: self.lineEdit_output.setText(QFileDialog.getExistingDirectory(
56 | self, "Set output directory", "./")))
57 |
58 | self.pushButton_start.clicked.connect(self.start_download)
59 | self.pushButton_cancel.clicked.connect(self.cancel_download)
60 |
61 | def log(self, text):
62 | if text.strip(" \n") == "":
63 | return
64 | self.log_queue.append(text)
65 |
66 | def progress_log(self):
67 | while len(self.log_queue) > 0:
68 | log_str = self.log_queue.pop(0)
69 | if log_str.startswith("=="):
70 | self.progressBar_current.setMaximum(int(log_str.split()[1]))
71 | if log_str.startswith("##"):
72 | self.progressBar_current.setValue(
73 | self.progressBar_current.value() + 1)
74 | log_str = "[" + QTime.currentTime().toString() + "] " + log_str
75 | self.plainTextEdit_log.appendPlainText(log_str)
76 |
77 | def reset_ui(self):
78 | self.progressBar_current.setFormat("")
79 | self.progressBar_current.reset()
80 | self.progressBar_total.setFormat("")
81 | self.progressBar_total.reset()
82 | self.label_time_elapsed.setText("00:00:00")
83 | self.plainTextEdit_log.clear()
84 |
85 | def update_elapsed_time(self):
86 | elapsed_total = self.elapsed_timer.elapsed() / 1000
87 | elapsed_hour = elapsed_total / 3600
88 | elapsed_minutes = (elapsed_total % 3600) / 60
89 | elapsed_secs = elapsed_total % 60
90 | str_elapsed_time = "%02d:%02d:%02d" % (
91 | elapsed_hour, elapsed_minutes, elapsed_secs)
92 | self.label_time_elapsed.setText(str_elapsed_time)
93 |
94 | def gen_config_from_ui(self):
95 |
96 | config = utils.AppConfig()
97 |
98 | """ Engine """
99 | if self.radioButton_google.isChecked():
100 | config.engine = "Google"
101 | elif self.radioButton_bing.isChecked():
102 | config.engine = "Bing"
103 | elif self.radioButton_baidu.isChecked():
104 | config.engine = "Baidu"
105 |
106 | """ Driver """
107 | if self.radioButton_chrome_headless.isChecked():
108 | config.driver = "chrome_headless"
109 | elif self.radioButton_chrome.isChecked():
110 | config.driver = "chrome"
111 | elif self.radioButton_api.isChecked():
112 | config.driver = "api"
113 |
114 | """ Output directory """
115 | config.output_dir = self.lineEdit_output.text()
116 |
117 | """ Switches """
118 | config.face_only = self.checkBox_face_only.isChecked()
119 | config.safe_mode = self.checkBox_safe_mode.isChecked()
120 |
121 | """ Numbers """
122 | config.max_number = self.spinBox_max_number.value()
123 | config.num_threads = self.spinBox_num_threads.value()
124 |
125 | """ Proxy """
126 | if self.checkBox_proxy.isChecked():
127 | if self.radioButton_http.isChecked():
128 | config.proxy_type = "http"
129 | elif self.radioButton_socks5.isChecked():
130 | config.proxy_type = "socks5"
131 | config.proxy = self.lineEdit_proxy.text()
132 | else:
133 | config.proxy_type = None
134 | config.proxy = None
135 |
136 | """ Keywords List """
137 | if self.checkBox_from_file.isChecked():
138 | str_path = self.lineEdit_path2file.text()
139 | keywords_list = utils.gen_keywords_list_from_file(str_path)
140 | else:
141 | str_keywords = self.lineEdit_keywords.text()
142 | keywords_list = utils.gen_keywords_list_from_str(str_keywords, ",")
143 |
144 | return config, keywords_list
145 |
146 | def start_download(self):
147 | if self.checkBox_from_file.isChecked() and self.lineEdit_path2file.text() == "" \
148 | or not self.checkBox_from_file.isChecked() and self.lineEdit_keywords.text() == "":
149 | print("Keywords is empty!")
150 | self.lineEdit_keywords.setFocus()
151 | return
152 |
153 | if self.lineEdit_output.text() == "":
154 | print("Output directory is empty!")
155 | self.lineEdit_output.setFocus()
156 | return
157 |
158 | self.state = "run"
159 | self.pushButton_start.setEnabled(False)
160 | self.pushButton_cancel.setEnabled(True)
161 |
162 | config, keywords_list = self.gen_config_from_ui()
163 |
164 | self.elapsed_timer.restart()
165 | self.update_timer.start()
166 |
167 | self.reset_ui()
168 | num_keywords = len(keywords_list)
169 |
170 | self.progressBar_total.setMaximum(num_keywords)
171 | self.progressBar_total.setFormat("%p%, %v/%m")
172 | self.progressBar_total.setValue(0)
173 |
174 | for index in range(num_keywords):
175 | if self.state != "run":
176 | break
177 | keywords = keywords_list[index].strip()
178 | if keywords == "":
179 | continue
180 |
181 | config.keywords = keywords
182 | str_paras = config.to_command_paras()
183 |
184 | print(str_paras)
185 |
186 | self.progressBar_current.setMaximum(config.max_number)
187 | self.progressBar_current.setValue(0)
188 | self.progressBar_current.setFormat(keywords + ", %p%, %v/%m")
189 |
190 | thread_download = Thread(target=image_downloader.main, args=[
191 | shlex.split(str_paras)])
192 | thread_download.start()
193 |
194 | while thread_download.is_alive():
195 | QTest.qWait(1000)
196 | if self.isHidden():
197 | os._exit(0)
198 |
199 | self.progressBar_total.setValue(index + 1)
200 |
201 | if self.state == "run":
202 | self.state = "stop"
203 | self.pushButton_cancel.setEnabled(False)
204 | self.pushButton_start.setEnabled(True)
205 | self.update_timer.stop()
206 | print("stopped")
207 | pass
208 |
209 | def cancel_download(self):
210 | self.state = "stop"
211 | self.pushButton_cancel.setEnabled(False)
212 |
213 | pass
214 |
--------------------------------------------------------------------------------
/mainwindow.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | MainWindow
4 |
5 |
6 |
7 | 0
8 | 0
9 | 800
10 | 718
11 |
12 |
13 |
14 |
15 | 0
16 | 0
17 |
18 |
19 |
20 | MainWindow
21 |
22 |
23 |
24 | -
25 |
26 |
27 |
28 | 0
29 | 0
30 |
31 |
32 |
33 |
34 | 10
35 |
36 |
37 |
38 | QGroupBox{border:1px ridge gray;margin-top: 1ex;} QGroupBox::title{subcontrol-origin: margin;subcontrol-position:top center;padding:0 3px;}
39 |
40 |
41 | Progress
42 |
43 |
44 |
45 | 30
46 |
47 |
-
48 |
49 |
50 |
51 | 12
52 |
53 |
54 |
55 | Time Elapsed:
56 |
57 |
58 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
59 |
60 |
61 |
62 | -
63 |
64 |
65 |
66 | Ubuntu Mono
67 | 12
68 |
69 |
70 |
71 | 00:00:00
72 |
73 |
74 |
75 | -
76 |
77 |
78 |
79 | 12
80 |
81 |
82 |
83 | Total Progress:
84 |
85 |
86 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
87 |
88 |
89 |
90 | -
91 |
92 |
93 |
94 | 0
95 | 0
96 |
97 |
98 |
99 |
100 | Ubuntu Mono
101 | 12
102 |
103 |
104 |
105 |
106 |
107 |
108 | 1000
109 |
110 |
111 | 0
112 |
113 |
114 | Qt::AlignCenter
115 |
116 |
117 |
118 |
119 |
120 |
121 | -
122 |
123 |
124 |
125 | 12
126 |
127 |
128 |
129 | Current Progress:
130 |
131 |
132 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
133 |
134 |
135 |
136 | -
137 |
138 |
139 |
140 | 0
141 | 0
142 |
143 |
144 |
145 |
146 | Ubuntu Mono
147 | 12
148 |
149 |
150 |
151 | /*
152 | QProgressBar {
153 | border: 2px solid gray;
154 | background-color: rgba(0, 0, 0, 0);
155 | }
156 |
157 | QProgressBar::chunk {
158 | background-color: rgba(0, 200, 0);
159 | width: 1px;
160 | }*/
161 |
162 |
163 | 1000
164 |
165 |
166 | 0
167 |
168 |
169 | Qt::AlignCenter
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 | -
180 |
181 |
182 |
183 | 10
184 |
185 |
186 |
187 | QGroupBox{border:1px ridge gray;margin-top: 1ex;} QGroupBox::title{subcontrol-origin: margin;subcontrol-position:top center;padding:0 3px;}
188 |
189 |
190 | Control
191 |
192 |
193 |
194 | 20
195 |
196 |
197 | 30
198 |
199 |
200 | 30
201 |
202 |
203 | 30
204 |
205 |
206 | 30
207 |
208 |
-
209 |
210 |
211 |
212 | 0
213 | 0
214 |
215 |
216 |
217 |
218 | 12
219 |
220 |
221 |
222 | Start
223 |
224 |
225 | Return
226 |
227 |
228 |
229 | -
230 |
231 |
232 | false
233 |
234 |
235 |
236 | 0
237 | 0
238 |
239 |
240 |
241 |
242 | 12
243 |
244 |
245 |
246 | &Cancel
247 |
248 |
249 |
250 | -
251 |
252 |
253 | Qt::Vertical
254 |
255 |
256 | QSizePolicy::Preferred
257 |
258 |
259 |
260 | 20
261 | 40
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 | -
270 |
271 |
272 |
273 | 0
274 | 0
275 |
276 |
277 |
278 |
279 | 10
280 |
281 |
282 |
283 | QGroupBox{border:1px ridge gray;margin-top: 1ex;} QGroupBox::title{subcontrol-origin: margin;subcontrol-position:top center;padding:0 3px;}
284 |
285 |
286 | Config
287 |
288 |
289 | false
290 |
291 |
292 | false
293 |
294 |
295 |
296 | 20
297 |
298 |
299 | 20
300 |
301 |
-
302 |
303 |
304 |
-
305 |
306 |
307 |
308 | 12
309 |
310 |
311 |
312 | Qt::StrongFocus
313 |
314 |
315 | &Google
316 |
317 |
318 | true
319 |
320 |
321 | buttonGroup
322 |
323 |
324 |
325 | -
326 |
327 |
328 |
329 | 12
330 |
331 |
332 |
333 | Qt::StrongFocus
334 |
335 |
336 | &Bing
337 |
338 |
339 | buttonGroup
340 |
341 |
342 |
343 | -
344 |
345 |
346 |
347 | 12
348 |
349 |
350 |
351 | Qt::StrongFocus
352 |
353 |
354 | B&aidu
355 |
356 |
357 | buttonGroup
358 |
359 |
360 |
361 |
362 |
363 |
364 | -
365 |
366 |
367 |
-
368 |
369 |
370 |
371 | 12
372 |
373 |
374 |
375 | ChromeHeadless
376 |
377 |
378 | true
379 |
380 |
381 |
382 | -
383 |
384 |
385 |
386 | 12
387 |
388 |
389 |
390 | Chrome
391 |
392 |
393 |
394 | -
395 |
396 |
397 | false
398 |
399 |
400 |
401 | 12
402 |
403 |
404 |
405 | API
406 |
407 |
408 |
409 |
410 |
411 |
412 | -
413 |
414 |
415 |
-
416 |
417 |
418 |
419 | 12
420 | 75
421 | true
422 |
423 |
424 |
425 | &Keywords:
426 |
427 |
428 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
429 |
430 |
431 | lineEdit_keywords
432 |
433 |
434 |
435 | -
436 |
437 |
438 |
439 | 12
440 |
441 |
442 |
443 | Input keywords, seperated by comma ", "
444 |
445 |
446 | Hint: e.g. To download image of Micheal Jordan and Yao Ming, input "Micheal Jordan, Yao Ming"
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 | Seperates by comma ( , )
456 |
457 |
458 |
459 | -
460 |
461 |
462 |
463 | 0
464 | 0
465 |
466 |
467 |
468 |
469 | 12
470 | false
471 |
472 |
473 |
474 | &Load File:
475 |
476 |
477 |
478 | -
479 |
480 |
481 | false
482 |
483 |
484 |
485 | 12
486 |
487 |
488 |
489 | Hint: Enter path to a text file. Each line of the file contains a group of keywords
490 |
491 |
492 | Path to file
493 |
494 |
495 |
496 | -
497 |
498 |
499 | false
500 |
501 |
502 |
503 | 0
504 | 0
505 |
506 |
507 |
508 |
509 | 12
510 |
511 |
512 |
513 | ...
514 |
515 |
516 |
517 |
518 |
519 |
520 | -
521 |
522 |
523 |
524 | 6
525 |
526 |
-
527 |
528 |
529 |
530 | 12
531 | 75
532 | true
533 |
534 |
535 |
536 | &Output:
537 |
538 |
539 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
540 |
541 |
542 | lineEdit_output
543 |
544 |
545 |
546 | -
547 |
548 |
549 |
550 | 0
551 | 0
552 |
553 |
554 |
555 |
556 | 12
557 |
558 |
559 |
560 | Path to output directory.
561 |
562 |
563 | Path to output directory.
564 |
565 |
566 |
567 |
568 |
569 | ./download_images
570 |
571 |
572 | Path to output directory.
573 |
574 |
575 |
576 | -
577 |
578 |
579 | true
580 |
581 |
582 |
583 | 0
584 | 0
585 |
586 |
587 |
588 |
589 | 12
590 |
591 |
592 |
593 | ...
594 |
595 |
596 |
597 |
598 |
599 |
600 | -
601 |
602 |
603 |
-
604 |
605 |
606 |
607 | 0
608 | 0
609 |
610 |
611 |
612 |
613 | 12
614 |
615 |
616 |
617 | &Face Only
618 |
619 |
620 |
621 | -
622 |
623 |
624 |
625 | 0
626 | 0
627 |
628 |
629 |
630 |
631 | 12
632 |
633 |
634 |
635 | &Safe Mode
636 |
637 |
638 | true
639 |
640 |
641 | true
642 |
643 |
644 |
645 | -
646 |
647 |
648 |
649 | 12
650 |
651 |
652 |
653 | Max &number
654 | per keywords
655 |
656 |
657 | spinBox_max_number
658 |
659 |
660 |
661 | -
662 |
663 |
664 |
665 | 0
666 | 0
667 |
668 |
669 |
670 |
671 | 0
672 | 40
673 |
674 |
675 |
676 |
677 | 12
678 |
679 |
680 |
681 | 1
682 |
683 |
684 | 2000
685 |
686 |
687 | 100
688 |
689 |
690 |
691 | -
692 |
693 |
694 |
695 | 12
696 |
697 |
698 |
699 | &Threads:
700 |
701 |
702 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
703 |
704 |
705 | spinBox_num_threads
706 |
707 |
708 |
709 | -
710 |
711 |
712 |
713 | 0
714 | 0
715 |
716 |
717 |
718 |
719 | 0
720 | 40
721 |
722 |
723 |
724 |
725 | 12
726 |
727 |
728 |
729 | 1
730 |
731 |
732 | 200
733 |
734 |
735 | 50
736 |
737 |
738 |
739 |
740 |
741 |
742 | -
743 |
744 |
745 |
746 | 9
747 |
748 |
749 | 9
750 |
751 |
-
752 |
753 |
754 |
755 | 0
756 | 0
757 |
758 |
759 |
760 |
761 | 12
762 |
763 |
764 |
765 | &Proxy:
766 |
767 |
768 |
769 | -
770 |
771 |
772 | false
773 |
774 |
775 |
776 | 0
777 |
778 |
779 | 0
780 |
781 |
782 | 0
783 |
784 |
785 | 0
786 |
787 |
-
788 |
789 |
790 |
791 | 0
792 | 0
793 |
794 |
795 |
796 |
797 | 12
798 |
799 |
800 |
801 | Qt::TabFocus
802 |
803 |
804 | HTTP
805 |
806 |
807 | true
808 |
809 |
810 | buttonGroup_2
811 |
812 |
813 |
814 | -
815 |
816 |
817 |
818 | 0
819 | 0
820 |
821 |
822 |
823 |
824 | 12
825 |
826 |
827 |
828 | Qt::TabFocus
829 |
830 |
831 | Socks5
832 |
833 |
834 | false
835 |
836 |
837 | buttonGroup_2
838 |
839 |
840 |
841 | -
842 |
843 |
844 |
845 | 12
846 |
847 |
848 |
849 | input ip:port
850 |
851 |
852 | xxx.xxx.xxx.xx:port
853 |
854 |
855 | xxx.xxx.xxx.xx:port
856 |
857 |
858 |
859 |
860 |
861 |
862 |
863 |
864 |
865 |
866 |
867 |
868 | -
869 |
870 |
871 |
872 | Ubuntu Mono
873 |
874 |
875 |
876 | true
877 |
878 |
879 |
880 |
881 |
882 | 4
883 |
884 |
885 | 0
886 |
887 |
888 |
889 |
890 |
891 |
908 |
909 |
910 |
911 | About
912 |
913 |
914 |
915 |
916 | lineEdit_keywords
917 | checkBox_from_file
918 | lineEdit_path2file
919 | pushButton_load_file
920 | lineEdit_output
921 | pushButton_output
922 | checkBox_face_only
923 | checkBox_safe_mode
924 | spinBox_max_number
925 | spinBox_num_threads
926 | checkBox_proxy
927 | radioButton_http
928 | radioButton_socks5
929 | lineEdit_proxy
930 | pushButton_start
931 | pushButton_cancel
932 | radioButton_google
933 | radioButton_bing
934 | radioButton_baidu
935 | plainTextEdit_log
936 |
937 |
938 |
939 |
940 | checkBox_from_file
941 | clicked(bool)
942 | lineEdit_keywords
943 | setDisabled(bool)
944 |
945 |
946 | 157
947 | 193
948 |
949 |
950 | 599
951 | 163
952 |
953 |
954 |
955 |
956 | checkBox_from_file
957 | clicked(bool)
958 | pushButton_load_file
959 | setEnabled(bool)
960 |
961 |
962 | 157
963 | 193
964 |
965 |
966 | 599
967 | 193
968 |
969 |
970 |
971 |
972 | checkBox_from_file
973 | clicked(bool)
974 | lineEdit_path2file
975 | setEnabled(bool)
976 |
977 |
978 | 157
979 | 193
980 |
981 |
982 | 518
983 | 192
984 |
985 |
986 |
987 |
988 | checkBox_proxy
989 | clicked(bool)
990 | widget_5
991 | setEnabled(bool)
992 |
993 |
994 | 131
995 | 334
996 |
997 |
998 | 491
999 | 334
1000 |
1001 |
1002 |
1003 |
1004 | checkBox_proxy
1005 | clicked(bool)
1006 | lineEdit_proxy
1007 | setFocus()
1008 |
1009 |
1010 | 131
1011 | 334
1012 |
1013 |
1014 | 599
1015 | 334
1016 |
1017 |
1018 |
1019 |
1020 | radioButton_google
1021 | toggled(bool)
1022 | checkBox_safe_mode
1023 | setEnabled(bool)
1024 |
1025 |
1026 | 204
1027 | 73
1028 |
1029 |
1030 | 220
1031 | 270
1032 |
1033 |
1034 |
1035 |
1036 | radioButton_bing
1037 | toggled(bool)
1038 | checkBox_safe_mode
1039 | setChecked(bool)
1040 |
1041 |
1042 | 258
1043 | 73
1044 |
1045 |
1046 | 293
1047 | 305
1048 |
1049 |
1050 |
1051 |
1052 | radioButton_baidu
1053 | toggled(bool)
1054 | checkBox_safe_mode
1055 | setChecked(bool)
1056 |
1057 |
1058 | 453
1059 | 68
1060 |
1061 |
1062 | 293
1063 | 305
1064 |
1065 |
1066 |
1067 |
1068 | radioButton_google
1069 | toggled(bool)
1070 | checkBox_safe_mode
1071 | setChecked(bool)
1072 |
1073 |
1074 | 122
1075 | 73
1076 |
1077 |
1078 | 293
1079 | 305
1080 |
1081 |
1082 |
1083 |
1084 | checkBox_from_file
1085 | clicked(bool)
1086 | pushButton_load_file
1087 | click()
1088 |
1089 |
1090 | 131
1091 | 193
1092 |
1093 |
1094 | 599
1095 | 193
1096 |
1097 |
1098 |
1099 |
1100 | radioButton_google
1101 | toggled(bool)
1102 | radioButton_api
1103 | setDisabled(bool)
1104 |
1105 |
1106 | 92
1107 | 60
1108 |
1109 |
1110 | 435
1111 | 107
1112 |
1113 |
1114 |
1115 |
1116 | radioButton_baidu
1117 | toggled(bool)
1118 | radioButton_api
1119 | setChecked(bool)
1120 |
1121 |
1122 | 437
1123 | 58
1124 |
1125 |
1126 | 568
1127 | 113
1128 |
1129 |
1130 |
1131 |
1132 | radioButton_baidu
1133 | toggled(bool)
1134 | radioButton_api
1135 | setEnabled(bool)
1136 |
1137 |
1138 | 505
1139 | 64
1140 |
1141 |
1142 | 546
1143 | 111
1144 |
1145 |
1146 |
1147 |
1148 | radioButton_google
1149 | toggled(bool)
1150 | radioButton_chrome_headless
1151 | setChecked(bool)
1152 |
1153 |
1154 | 67
1155 | 67
1156 |
1157 |
1158 | 63
1159 | 103
1160 |
1161 |
1162 |
1163 |
1164 | radioButton_bing
1165 | toggled(bool)
1166 | radioButton_api
1167 | setChecked(bool)
1168 |
1169 |
1170 | 381
1171 | 69
1172 |
1173 |
1174 | 464
1175 | 111
1176 |
1177 |
1178 |
1179 |
1180 | radioButton_bing
1181 | toggled(bool)
1182 | radioButton_api
1183 | setEnabled(bool)
1184 |
1185 |
1186 | 322
1187 | 68
1188 |
1189 |
1190 | 460
1191 | 114
1192 |
1193 |
1194 |
1195 |
1196 |
1197 |
1198 |
1199 |
1200 |
1201 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | chromedriver-autoinstaller==0.4.0
2 | pyinstaller==5.9.0
3 | PyQt5==5.15.9
4 | requests==2.31.0
5 | selenium==4.8.3
6 |
--------------------------------------------------------------------------------
/ui_about.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Form implementation generated from reading ui file '.\about.ui'
4 | #
5 | # Created by: PyQt5 UI code generator 5.6
6 | #
7 | # WARNING! All changes made in this file will be lost!
8 |
9 | from PyQt5 import QtCore, QtGui, QtWidgets
10 |
11 | class Ui_Dialog_about(object):
12 | def setupUi(self, Dialog_about):
13 | Dialog_about.setObjectName("Dialog_about")
14 | Dialog_about.resize(600, 200)
15 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
16 | sizePolicy.setHorizontalStretch(0)
17 | sizePolicy.setVerticalStretch(0)
18 | sizePolicy.setHeightForWidth(Dialog_about.sizePolicy().hasHeightForWidth())
19 | Dialog_about.setSizePolicy(sizePolicy)
20 | self.gridLayout = QtWidgets.QGridLayout(Dialog_about)
21 | self.gridLayout.setObjectName("gridLayout")
22 | spacerItem = QtWidgets.QSpacerItem(20, 43, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
23 | self.gridLayout.addItem(spacerItem, 0, 1, 1, 1)
24 | self.label = QtWidgets.QLabel(Dialog_about)
25 | font = QtGui.QFont()
26 | font.setPointSize(10)
27 | self.label.setFont(font)
28 | self.label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
29 | self.label.setObjectName("label")
30 | self.gridLayout.addWidget(self.label, 1, 1, 1, 1)
31 | self.lineEdit = QtWidgets.QLineEdit(Dialog_about)
32 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
33 | sizePolicy.setHorizontalStretch(0)
34 | sizePolicy.setVerticalStretch(0)
35 | sizePolicy.setHeightForWidth(self.lineEdit.sizePolicy().hasHeightForWidth())
36 | self.lineEdit.setSizePolicy(sizePolicy)
37 | font = QtGui.QFont()
38 | font.setPointSize(10)
39 | self.lineEdit.setFont(font)
40 | self.lineEdit.setFocusPolicy(QtCore.Qt.NoFocus)
41 | self.lineEdit.setCursorPosition(0)
42 | self.lineEdit.setReadOnly(True)
43 | self.lineEdit.setObjectName("lineEdit")
44 | self.gridLayout.addWidget(self.lineEdit, 1, 2, 1, 1)
45 | spacerItem1 = QtWidgets.QSpacerItem(34, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
46 | self.gridLayout.addItem(spacerItem1, 2, 0, 1, 1)
47 | self.label_3 = QtWidgets.QLabel(Dialog_about)
48 | font = QtGui.QFont()
49 | font.setPointSize(10)
50 | self.label_3.setFont(font)
51 | self.label_3.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
52 | self.label_3.setObjectName("label_3")
53 | self.gridLayout.addWidget(self.label_3, 2, 1, 1, 1)
54 | self.lineEdit_2 = QtWidgets.QLineEdit(Dialog_about)
55 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
56 | sizePolicy.setHorizontalStretch(0)
57 | sizePolicy.setVerticalStretch(0)
58 | sizePolicy.setHeightForWidth(self.lineEdit_2.sizePolicy().hasHeightForWidth())
59 | self.lineEdit_2.setSizePolicy(sizePolicy)
60 | font = QtGui.QFont()
61 | font.setPointSize(10)
62 | self.lineEdit_2.setFont(font)
63 | self.lineEdit_2.setFocusPolicy(QtCore.Qt.NoFocus)
64 | self.lineEdit_2.setCursorPosition(0)
65 | self.lineEdit_2.setReadOnly(True)
66 | self.lineEdit_2.setObjectName("lineEdit_2")
67 | self.gridLayout.addWidget(self.lineEdit_2, 2, 2, 1, 1)
68 | spacerItem2 = QtWidgets.QSpacerItem(34, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
69 | self.gridLayout.addItem(spacerItem2, 2, 3, 1, 1)
70 | self.label_5 = QtWidgets.QLabel(Dialog_about)
71 | font = QtGui.QFont()
72 | font.setPointSize(10)
73 | self.label_5.setFont(font)
74 | self.label_5.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
75 | self.label_5.setObjectName("label_5")
76 | self.gridLayout.addWidget(self.label_5, 3, 1, 1, 1)
77 | self.lineEdit_3 = QtWidgets.QLineEdit(Dialog_about)
78 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
79 | sizePolicy.setHorizontalStretch(0)
80 | sizePolicy.setVerticalStretch(0)
81 | sizePolicy.setHeightForWidth(self.lineEdit_3.sizePolicy().hasHeightForWidth())
82 | self.lineEdit_3.setSizePolicy(sizePolicy)
83 | font = QtGui.QFont()
84 | font.setPointSize(10)
85 | self.lineEdit_3.setFont(font)
86 | self.lineEdit_3.setFocusPolicy(QtCore.Qt.NoFocus)
87 | self.lineEdit_3.setCursorPosition(0)
88 | self.lineEdit_3.setReadOnly(True)
89 | self.lineEdit_3.setObjectName("lineEdit_3")
90 | self.gridLayout.addWidget(self.lineEdit_3, 3, 2, 1, 1)
91 | spacerItem3 = QtWidgets.QSpacerItem(20, 42, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
92 | self.gridLayout.addItem(spacerItem3, 4, 1, 1, 1)
93 | self.gridLayout.setColumnStretch(0, 1)
94 | self.gridLayout.setColumnStretch(1, 2)
95 | self.gridLayout.setColumnStretch(2, 11)
96 | self.gridLayout.setColumnStretch(3, 1)
97 |
98 | self.retranslateUi(Dialog_about)
99 | QtCore.QMetaObject.connectSlotsByName(Dialog_about)
100 |
101 | def retranslateUi(self, Dialog_about):
102 | _translate = QtCore.QCoreApplication.translate
103 | Dialog_about.setWindowTitle(_translate("Dialog_about", "Dialog"))
104 | self.label.setText(_translate("Dialog_about", "Author:"))
105 | self.lineEdit.setText(_translate("Dialog_about", "Yabin Zheng"))
106 | self.label_3.setText(_translate("Dialog_about", "Email:"))
107 | self.lineEdit_2.setText(_translate("Dialog_about", "sczhengyabin@hotmail.com"))
108 | self.label_5.setText(_translate("Dialog_about", "Project Home:"))
109 | self.lineEdit_3.setText(_translate("Dialog_about", "https://github.com/sczhengyabin/Google-Image-Downloader"))
110 |
111 |
--------------------------------------------------------------------------------
/ui_mainwindow.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Form implementation generated from reading ui file '.\mainwindow.ui'
4 | #
5 | # Created by: PyQt5 UI code generator 5.15.9
6 | #
7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is
8 | # run again. Do not edit this file unless you know what you are doing.
9 |
10 |
11 | from PyQt5 import QtCore, QtGui, QtWidgets
12 |
13 |
14 | class Ui_MainWindow(object):
15 | def setupUi(self, MainWindow):
16 | MainWindow.setObjectName("MainWindow")
17 | MainWindow.resize(800, 718)
18 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
19 | sizePolicy.setHorizontalStretch(0)
20 | sizePolicy.setVerticalStretch(0)
21 | sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth())
22 | MainWindow.setSizePolicy(sizePolicy)
23 | self.centralwidget = QtWidgets.QWidget(MainWindow)
24 | self.centralwidget.setObjectName("centralwidget")
25 | self.gridLayout_3 = QtWidgets.QGridLayout(self.centralwidget)
26 | self.gridLayout_3.setObjectName("gridLayout_3")
27 | self.groupBox_3 = QtWidgets.QGroupBox(self.centralwidget)
28 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
29 | sizePolicy.setHorizontalStretch(0)
30 | sizePolicy.setVerticalStretch(0)
31 | sizePolicy.setHeightForWidth(self.groupBox_3.sizePolicy().hasHeightForWidth())
32 | self.groupBox_3.setSizePolicy(sizePolicy)
33 | font = QtGui.QFont()
34 | font.setPointSize(10)
35 | self.groupBox_3.setFont(font)
36 | self.groupBox_3.setStyleSheet("QGroupBox{border:1px ridge gray;margin-top: 1ex;} QGroupBox::title{subcontrol-origin: margin;subcontrol-position:top center;padding:0 3px;}")
37 | self.groupBox_3.setObjectName("groupBox_3")
38 | self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox_3)
39 | self.gridLayout_2.setContentsMargins(-1, -1, 30, -1)
40 | self.gridLayout_2.setObjectName("gridLayout_2")
41 | self.label_4 = QtWidgets.QLabel(self.groupBox_3)
42 | font = QtGui.QFont()
43 | font.setPointSize(12)
44 | self.label_4.setFont(font)
45 | self.label_4.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
46 | self.label_4.setObjectName("label_4")
47 | self.gridLayout_2.addWidget(self.label_4, 0, 0, 1, 1)
48 | self.label_time_elapsed = QtWidgets.QLabel(self.groupBox_3)
49 | font = QtGui.QFont()
50 | font.setFamily("Ubuntu Mono")
51 | font.setPointSize(12)
52 | self.label_time_elapsed.setFont(font)
53 | self.label_time_elapsed.setObjectName("label_time_elapsed")
54 | self.gridLayout_2.addWidget(self.label_time_elapsed, 0, 1, 1, 1)
55 | self.label_2 = QtWidgets.QLabel(self.groupBox_3)
56 | font = QtGui.QFont()
57 | font.setPointSize(12)
58 | self.label_2.setFont(font)
59 | self.label_2.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
60 | self.label_2.setObjectName("label_2")
61 | self.gridLayout_2.addWidget(self.label_2, 1, 0, 1, 1)
62 | self.progressBar_total = QtWidgets.QProgressBar(self.groupBox_3)
63 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
64 | sizePolicy.setHorizontalStretch(0)
65 | sizePolicy.setVerticalStretch(0)
66 | sizePolicy.setHeightForWidth(self.progressBar_total.sizePolicy().hasHeightForWidth())
67 | self.progressBar_total.setSizePolicy(sizePolicy)
68 | font = QtGui.QFont()
69 | font.setFamily("Ubuntu Mono")
70 | font.setPointSize(12)
71 | self.progressBar_total.setFont(font)
72 | self.progressBar_total.setStyleSheet("")
73 | self.progressBar_total.setMaximum(1000)
74 | self.progressBar_total.setProperty("value", 0)
75 | self.progressBar_total.setAlignment(QtCore.Qt.AlignCenter)
76 | self.progressBar_total.setFormat("")
77 | self.progressBar_total.setObjectName("progressBar_total")
78 | self.gridLayout_2.addWidget(self.progressBar_total, 1, 1, 1, 1)
79 | self.label_3 = QtWidgets.QLabel(self.groupBox_3)
80 | font = QtGui.QFont()
81 | font.setPointSize(12)
82 | self.label_3.setFont(font)
83 | self.label_3.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
84 | self.label_3.setObjectName("label_3")
85 | self.gridLayout_2.addWidget(self.label_3, 2, 0, 1, 1)
86 | self.progressBar_current = QtWidgets.QProgressBar(self.groupBox_3)
87 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
88 | sizePolicy.setHorizontalStretch(0)
89 | sizePolicy.setVerticalStretch(0)
90 | sizePolicy.setHeightForWidth(self.progressBar_current.sizePolicy().hasHeightForWidth())
91 | self.progressBar_current.setSizePolicy(sizePolicy)
92 | font = QtGui.QFont()
93 | font.setFamily("Ubuntu Mono")
94 | font.setPointSize(12)
95 | self.progressBar_current.setFont(font)
96 | self.progressBar_current.setStyleSheet("/*\n"
97 | "QProgressBar {\n"
98 | " border: 2px solid gray;\n"
99 | " background-color: rgba(0, 0, 0, 0);\n"
100 | "}\n"
101 | "\n"
102 | "QProgressBar::chunk {\n"
103 | " background-color: rgba(0, 200, 0);\n"
104 | " width: 1px;\n"
105 | "}*/")
106 | self.progressBar_current.setMaximum(1000)
107 | self.progressBar_current.setProperty("value", 0)
108 | self.progressBar_current.setAlignment(QtCore.Qt.AlignCenter)
109 | self.progressBar_current.setFormat("")
110 | self.progressBar_current.setObjectName("progressBar_current")
111 | self.gridLayout_2.addWidget(self.progressBar_current, 2, 1, 1, 1)
112 | self.gridLayout_2.setColumnStretch(0, 1)
113 | self.gridLayout_2.setColumnStretch(1, 3)
114 | self.gridLayout_3.addWidget(self.groupBox_3, 1, 0, 1, 2)
115 | self.groupBox_2 = QtWidgets.QGroupBox(self.centralwidget)
116 | font = QtGui.QFont()
117 | font.setPointSize(10)
118 | self.groupBox_2.setFont(font)
119 | self.groupBox_2.setStyleSheet("QGroupBox{border:1px ridge gray;margin-top: 1ex;} QGroupBox::title{subcontrol-origin: margin;subcontrol-position:top center;padding:0 3px;}")
120 | self.groupBox_2.setObjectName("groupBox_2")
121 | self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox_2)
122 | self.verticalLayout_2.setContentsMargins(30, 30, 30, 30)
123 | self.verticalLayout_2.setSpacing(20)
124 | self.verticalLayout_2.setObjectName("verticalLayout_2")
125 | self.pushButton_start = QtWidgets.QPushButton(self.groupBox_2)
126 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
127 | sizePolicy.setHorizontalStretch(0)
128 | sizePolicy.setVerticalStretch(0)
129 | sizePolicy.setHeightForWidth(self.pushButton_start.sizePolicy().hasHeightForWidth())
130 | self.pushButton_start.setSizePolicy(sizePolicy)
131 | font = QtGui.QFont()
132 | font.setPointSize(12)
133 | self.pushButton_start.setFont(font)
134 | self.pushButton_start.setObjectName("pushButton_start")
135 | self.verticalLayout_2.addWidget(self.pushButton_start)
136 | self.pushButton_cancel = QtWidgets.QPushButton(self.groupBox_2)
137 | self.pushButton_cancel.setEnabled(False)
138 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
139 | sizePolicy.setHorizontalStretch(0)
140 | sizePolicy.setVerticalStretch(0)
141 | sizePolicy.setHeightForWidth(self.pushButton_cancel.sizePolicy().hasHeightForWidth())
142 | self.pushButton_cancel.setSizePolicy(sizePolicy)
143 | font = QtGui.QFont()
144 | font.setPointSize(12)
145 | self.pushButton_cancel.setFont(font)
146 | self.pushButton_cancel.setObjectName("pushButton_cancel")
147 | self.verticalLayout_2.addWidget(self.pushButton_cancel)
148 | spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred)
149 | self.verticalLayout_2.addItem(spacerItem)
150 | self.verticalLayout_2.setStretch(0, 1)
151 | self.verticalLayout_2.setStretch(1, 1)
152 | self.verticalLayout_2.setStretch(2, 1)
153 | self.gridLayout_3.addWidget(self.groupBox_2, 0, 1, 1, 1)
154 | self.groupBox_config = QtWidgets.QGroupBox(self.centralwidget)
155 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
156 | sizePolicy.setHorizontalStretch(0)
157 | sizePolicy.setVerticalStretch(0)
158 | sizePolicy.setHeightForWidth(self.groupBox_config.sizePolicy().hasHeightForWidth())
159 | self.groupBox_config.setSizePolicy(sizePolicy)
160 | font = QtGui.QFont()
161 | font.setPointSize(10)
162 | self.groupBox_config.setFont(font)
163 | self.groupBox_config.setStyleSheet("QGroupBox{border:1px ridge gray;margin-top: 1ex;} QGroupBox::title{subcontrol-origin: margin;subcontrol-position:top center;padding:0 3px;}")
164 | self.groupBox_config.setFlat(False)
165 | self.groupBox_config.setCheckable(False)
166 | self.groupBox_config.setObjectName("groupBox_config")
167 | self.verticalLayout = QtWidgets.QVBoxLayout(self.groupBox_config)
168 | self.verticalLayout.setContentsMargins(20, -1, 20, -1)
169 | self.verticalLayout.setObjectName("verticalLayout")
170 | self.widget_engine = QtWidgets.QWidget(self.groupBox_config)
171 | self.widget_engine.setObjectName("widget_engine")
172 | self.horizontalLayout = QtWidgets.QHBoxLayout(self.widget_engine)
173 | self.horizontalLayout.setObjectName("horizontalLayout")
174 | self.radioButton_google = QtWidgets.QRadioButton(self.widget_engine)
175 | font = QtGui.QFont()
176 | font.setPointSize(12)
177 | self.radioButton_google.setFont(font)
178 | self.radioButton_google.setFocusPolicy(QtCore.Qt.StrongFocus)
179 | self.radioButton_google.setChecked(True)
180 | self.radioButton_google.setObjectName("radioButton_google")
181 | self.buttonGroup = QtWidgets.QButtonGroup(MainWindow)
182 | self.buttonGroup.setObjectName("buttonGroup")
183 | self.buttonGroup.addButton(self.radioButton_google)
184 | self.horizontalLayout.addWidget(self.radioButton_google)
185 | self.radioButton_bing = QtWidgets.QRadioButton(self.widget_engine)
186 | font = QtGui.QFont()
187 | font.setPointSize(12)
188 | self.radioButton_bing.setFont(font)
189 | self.radioButton_bing.setFocusPolicy(QtCore.Qt.StrongFocus)
190 | self.radioButton_bing.setObjectName("radioButton_bing")
191 | self.buttonGroup.addButton(self.radioButton_bing)
192 | self.horizontalLayout.addWidget(self.radioButton_bing)
193 | self.radioButton_baidu = QtWidgets.QRadioButton(self.widget_engine)
194 | font = QtGui.QFont()
195 | font.setPointSize(12)
196 | self.radioButton_baidu.setFont(font)
197 | self.radioButton_baidu.setFocusPolicy(QtCore.Qt.StrongFocus)
198 | self.radioButton_baidu.setObjectName("radioButton_baidu")
199 | self.buttonGroup.addButton(self.radioButton_baidu)
200 | self.horizontalLayout.addWidget(self.radioButton_baidu)
201 | self.verticalLayout.addWidget(self.widget_engine)
202 | self.widget_driver = QtWidgets.QWidget(self.groupBox_config)
203 | self.widget_driver.setObjectName("widget_driver")
204 | self.horizontalLayout_6 = QtWidgets.QHBoxLayout(self.widget_driver)
205 | self.horizontalLayout_6.setObjectName("horizontalLayout_6")
206 | self.radioButton_chrome_headless = QtWidgets.QRadioButton(self.widget_driver)
207 | font = QtGui.QFont()
208 | font.setPointSize(12)
209 | self.radioButton_chrome_headless.setFont(font)
210 | self.radioButton_chrome_headless.setChecked(True)
211 | self.radioButton_chrome_headless.setObjectName("radioButton_chrome_headless")
212 | self.horizontalLayout_6.addWidget(self.radioButton_chrome_headless)
213 | self.radioButton_chrome = QtWidgets.QRadioButton(self.widget_driver)
214 | font = QtGui.QFont()
215 | font.setPointSize(12)
216 | self.radioButton_chrome.setFont(font)
217 | self.radioButton_chrome.setObjectName("radioButton_chrome")
218 | self.horizontalLayout_6.addWidget(self.radioButton_chrome)
219 | self.radioButton_api = QtWidgets.QRadioButton(self.widget_driver)
220 | self.radioButton_api.setEnabled(False)
221 | font = QtGui.QFont()
222 | font.setPointSize(12)
223 | self.radioButton_api.setFont(font)
224 | self.radioButton_api.setObjectName("radioButton_api")
225 | self.horizontalLayout_6.addWidget(self.radioButton_api)
226 | self.verticalLayout.addWidget(self.widget_driver)
227 | self.widget_keywords = QtWidgets.QWidget(self.groupBox_config)
228 | self.widget_keywords.setObjectName("widget_keywords")
229 | self.gridLayout = QtWidgets.QGridLayout(self.widget_keywords)
230 | self.gridLayout.setObjectName("gridLayout")
231 | self.label = QtWidgets.QLabel(self.widget_keywords)
232 | font = QtGui.QFont()
233 | font.setPointSize(12)
234 | font.setBold(True)
235 | font.setWeight(75)
236 | self.label.setFont(font)
237 | self.label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
238 | self.label.setObjectName("label")
239 | self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
240 | self.lineEdit_keywords = QtWidgets.QLineEdit(self.widget_keywords)
241 | font = QtGui.QFont()
242 | font.setPointSize(12)
243 | self.lineEdit_keywords.setFont(font)
244 | self.lineEdit_keywords.setWhatsThis("")
245 | self.lineEdit_keywords.setInputMask("")
246 | self.lineEdit_keywords.setObjectName("lineEdit_keywords")
247 | self.gridLayout.addWidget(self.lineEdit_keywords, 0, 1, 1, 2)
248 | self.checkBox_from_file = QtWidgets.QCheckBox(self.widget_keywords)
249 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred)
250 | sizePolicy.setHorizontalStretch(0)
251 | sizePolicy.setVerticalStretch(0)
252 | sizePolicy.setHeightForWidth(self.checkBox_from_file.sizePolicy().hasHeightForWidth())
253 | self.checkBox_from_file.setSizePolicy(sizePolicy)
254 | font = QtGui.QFont()
255 | font.setPointSize(12)
256 | font.setUnderline(False)
257 | self.checkBox_from_file.setFont(font)
258 | self.checkBox_from_file.setObjectName("checkBox_from_file")
259 | self.gridLayout.addWidget(self.checkBox_from_file, 1, 0, 1, 1)
260 | self.lineEdit_path2file = QtWidgets.QLineEdit(self.widget_keywords)
261 | self.lineEdit_path2file.setEnabled(False)
262 | font = QtGui.QFont()
263 | font.setPointSize(12)
264 | self.lineEdit_path2file.setFont(font)
265 | self.lineEdit_path2file.setObjectName("lineEdit_path2file")
266 | self.gridLayout.addWidget(self.lineEdit_path2file, 1, 1, 1, 1)
267 | self.pushButton_load_file = QtWidgets.QPushButton(self.widget_keywords)
268 | self.pushButton_load_file.setEnabled(False)
269 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
270 | sizePolicy.setHorizontalStretch(0)
271 | sizePolicy.setVerticalStretch(0)
272 | sizePolicy.setHeightForWidth(self.pushButton_load_file.sizePolicy().hasHeightForWidth())
273 | self.pushButton_load_file.setSizePolicy(sizePolicy)
274 | font = QtGui.QFont()
275 | font.setPointSize(12)
276 | self.pushButton_load_file.setFont(font)
277 | self.pushButton_load_file.setObjectName("pushButton_load_file")
278 | self.gridLayout.addWidget(self.pushButton_load_file, 1, 2, 1, 1)
279 | self.gridLayout.setColumnStretch(0, 2)
280 | self.gridLayout.setColumnStretch(1, 6)
281 | self.gridLayout.setColumnStretch(2, 1)
282 | self.verticalLayout.addWidget(self.widget_keywords)
283 | self.widget_output = QtWidgets.QWidget(self.groupBox_config)
284 | self.widget_output.setObjectName("widget_output")
285 | self.horizontalLayout_5 = QtWidgets.QHBoxLayout(self.widget_output)
286 | self.horizontalLayout_5.setSpacing(6)
287 | self.horizontalLayout_5.setObjectName("horizontalLayout_5")
288 | self.label_7 = QtWidgets.QLabel(self.widget_output)
289 | font = QtGui.QFont()
290 | font.setPointSize(12)
291 | font.setBold(True)
292 | font.setWeight(75)
293 | self.label_7.setFont(font)
294 | self.label_7.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
295 | self.label_7.setObjectName("label_7")
296 | self.horizontalLayout_5.addWidget(self.label_7)
297 | self.lineEdit_output = QtWidgets.QLineEdit(self.widget_output)
298 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
299 | sizePolicy.setHorizontalStretch(0)
300 | sizePolicy.setVerticalStretch(0)
301 | sizePolicy.setHeightForWidth(self.lineEdit_output.sizePolicy().hasHeightForWidth())
302 | self.lineEdit_output.setSizePolicy(sizePolicy)
303 | font = QtGui.QFont()
304 | font.setPointSize(12)
305 | self.lineEdit_output.setFont(font)
306 | self.lineEdit_output.setInputMask("")
307 | self.lineEdit_output.setObjectName("lineEdit_output")
308 | self.horizontalLayout_5.addWidget(self.lineEdit_output)
309 | self.pushButton_output = QtWidgets.QPushButton(self.widget_output)
310 | self.pushButton_output.setEnabled(True)
311 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
312 | sizePolicy.setHorizontalStretch(0)
313 | sizePolicy.setVerticalStretch(0)
314 | sizePolicy.setHeightForWidth(self.pushButton_output.sizePolicy().hasHeightForWidth())
315 | self.pushButton_output.setSizePolicy(sizePolicy)
316 | font = QtGui.QFont()
317 | font.setPointSize(12)
318 | self.pushButton_output.setFont(font)
319 | self.pushButton_output.setObjectName("pushButton_output")
320 | self.horizontalLayout_5.addWidget(self.pushButton_output)
321 | self.horizontalLayout_5.setStretch(0, 2)
322 | self.horizontalLayout_5.setStretch(1, 6)
323 | self.horizontalLayout_5.setStretch(2, 1)
324 | self.verticalLayout.addWidget(self.widget_output)
325 | self.widget_3 = QtWidgets.QWidget(self.groupBox_config)
326 | self.widget_3.setObjectName("widget_3")
327 | self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.widget_3)
328 | self.horizontalLayout_4.setObjectName("horizontalLayout_4")
329 | self.checkBox_face_only = QtWidgets.QCheckBox(self.widget_3)
330 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
331 | sizePolicy.setHorizontalStretch(0)
332 | sizePolicy.setVerticalStretch(0)
333 | sizePolicy.setHeightForWidth(self.checkBox_face_only.sizePolicy().hasHeightForWidth())
334 | self.checkBox_face_only.setSizePolicy(sizePolicy)
335 | font = QtGui.QFont()
336 | font.setPointSize(12)
337 | self.checkBox_face_only.setFont(font)
338 | self.checkBox_face_only.setObjectName("checkBox_face_only")
339 | self.horizontalLayout_4.addWidget(self.checkBox_face_only)
340 | self.checkBox_safe_mode = QtWidgets.QCheckBox(self.widget_3)
341 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
342 | sizePolicy.setHorizontalStretch(0)
343 | sizePolicy.setVerticalStretch(0)
344 | sizePolicy.setHeightForWidth(self.checkBox_safe_mode.sizePolicy().hasHeightForWidth())
345 | self.checkBox_safe_mode.setSizePolicy(sizePolicy)
346 | font = QtGui.QFont()
347 | font.setPointSize(12)
348 | self.checkBox_safe_mode.setFont(font)
349 | self.checkBox_safe_mode.setCheckable(True)
350 | self.checkBox_safe_mode.setChecked(True)
351 | self.checkBox_safe_mode.setObjectName("checkBox_safe_mode")
352 | self.horizontalLayout_4.addWidget(self.checkBox_safe_mode)
353 | self.label_6 = QtWidgets.QLabel(self.widget_3)
354 | font = QtGui.QFont()
355 | font.setPointSize(12)
356 | self.label_6.setFont(font)
357 | self.label_6.setObjectName("label_6")
358 | self.horizontalLayout_4.addWidget(self.label_6)
359 | self.spinBox_max_number = QtWidgets.QSpinBox(self.widget_3)
360 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
361 | sizePolicy.setHorizontalStretch(0)
362 | sizePolicy.setVerticalStretch(0)
363 | sizePolicy.setHeightForWidth(self.spinBox_max_number.sizePolicy().hasHeightForWidth())
364 | self.spinBox_max_number.setSizePolicy(sizePolicy)
365 | self.spinBox_max_number.setMinimumSize(QtCore.QSize(0, 40))
366 | font = QtGui.QFont()
367 | font.setPointSize(12)
368 | self.spinBox_max_number.setFont(font)
369 | self.spinBox_max_number.setMinimum(1)
370 | self.spinBox_max_number.setMaximum(2000)
371 | self.spinBox_max_number.setProperty("value", 100)
372 | self.spinBox_max_number.setObjectName("spinBox_max_number")
373 | self.horizontalLayout_4.addWidget(self.spinBox_max_number)
374 | self.label_5 = QtWidgets.QLabel(self.widget_3)
375 | font = QtGui.QFont()
376 | font.setPointSize(12)
377 | self.label_5.setFont(font)
378 | self.label_5.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
379 | self.label_5.setObjectName("label_5")
380 | self.horizontalLayout_4.addWidget(self.label_5)
381 | self.spinBox_num_threads = QtWidgets.QSpinBox(self.widget_3)
382 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
383 | sizePolicy.setHorizontalStretch(0)
384 | sizePolicy.setVerticalStretch(0)
385 | sizePolicy.setHeightForWidth(self.spinBox_num_threads.sizePolicy().hasHeightForWidth())
386 | self.spinBox_num_threads.setSizePolicy(sizePolicy)
387 | self.spinBox_num_threads.setMinimumSize(QtCore.QSize(0, 40))
388 | font = QtGui.QFont()
389 | font.setPointSize(12)
390 | self.spinBox_num_threads.setFont(font)
391 | self.spinBox_num_threads.setMinimum(1)
392 | self.spinBox_num_threads.setMaximum(200)
393 | self.spinBox_num_threads.setProperty("value", 50)
394 | self.spinBox_num_threads.setObjectName("spinBox_num_threads")
395 | self.horizontalLayout_4.addWidget(self.spinBox_num_threads)
396 | self.horizontalLayout_4.setStretch(0, 2)
397 | self.horizontalLayout_4.setStretch(1, 2)
398 | self.horizontalLayout_4.setStretch(4, 1)
399 | self.horizontalLayout_4.setStretch(5, 1)
400 | self.verticalLayout.addWidget(self.widget_3)
401 | self.widget_proxy = QtWidgets.QWidget(self.groupBox_config)
402 | self.widget_proxy.setObjectName("widget_proxy")
403 | self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.widget_proxy)
404 | self.horizontalLayout_2.setContentsMargins(-1, 9, -1, 9)
405 | self.horizontalLayout_2.setObjectName("horizontalLayout_2")
406 | self.checkBox_proxy = QtWidgets.QCheckBox(self.widget_proxy)
407 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred)
408 | sizePolicy.setHorizontalStretch(0)
409 | sizePolicy.setVerticalStretch(0)
410 | sizePolicy.setHeightForWidth(self.checkBox_proxy.sizePolicy().hasHeightForWidth())
411 | self.checkBox_proxy.setSizePolicy(sizePolicy)
412 | font = QtGui.QFont()
413 | font.setPointSize(12)
414 | self.checkBox_proxy.setFont(font)
415 | self.checkBox_proxy.setObjectName("checkBox_proxy")
416 | self.horizontalLayout_2.addWidget(self.checkBox_proxy)
417 | self.widget_5 = QtWidgets.QWidget(self.widget_proxy)
418 | self.widget_5.setEnabled(False)
419 | self.widget_5.setObjectName("widget_5")
420 | self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.widget_5)
421 | self.horizontalLayout_3.setContentsMargins(0, 0, 0, 0)
422 | self.horizontalLayout_3.setObjectName("horizontalLayout_3")
423 | self.radioButton_http = QtWidgets.QRadioButton(self.widget_5)
424 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
425 | sizePolicy.setHorizontalStretch(0)
426 | sizePolicy.setVerticalStretch(0)
427 | sizePolicy.setHeightForWidth(self.radioButton_http.sizePolicy().hasHeightForWidth())
428 | self.radioButton_http.setSizePolicy(sizePolicy)
429 | font = QtGui.QFont()
430 | font.setPointSize(12)
431 | self.radioButton_http.setFont(font)
432 | self.radioButton_http.setFocusPolicy(QtCore.Qt.TabFocus)
433 | self.radioButton_http.setChecked(True)
434 | self.radioButton_http.setObjectName("radioButton_http")
435 | self.buttonGroup_2 = QtWidgets.QButtonGroup(MainWindow)
436 | self.buttonGroup_2.setObjectName("buttonGroup_2")
437 | self.buttonGroup_2.addButton(self.radioButton_http)
438 | self.horizontalLayout_3.addWidget(self.radioButton_http)
439 | self.radioButton_socks5 = QtWidgets.QRadioButton(self.widget_5)
440 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
441 | sizePolicy.setHorizontalStretch(0)
442 | sizePolicy.setVerticalStretch(0)
443 | sizePolicy.setHeightForWidth(self.radioButton_socks5.sizePolicy().hasHeightForWidth())
444 | self.radioButton_socks5.setSizePolicy(sizePolicy)
445 | font = QtGui.QFont()
446 | font.setPointSize(12)
447 | self.radioButton_socks5.setFont(font)
448 | self.radioButton_socks5.setFocusPolicy(QtCore.Qt.TabFocus)
449 | self.radioButton_socks5.setChecked(False)
450 | self.radioButton_socks5.setObjectName("radioButton_socks5")
451 | self.buttonGroup_2.addButton(self.radioButton_socks5)
452 | self.horizontalLayout_3.addWidget(self.radioButton_socks5)
453 | self.lineEdit_proxy = QtWidgets.QLineEdit(self.widget_5)
454 | font = QtGui.QFont()
455 | font.setPointSize(12)
456 | self.lineEdit_proxy.setFont(font)
457 | self.lineEdit_proxy.setObjectName("lineEdit_proxy")
458 | self.horizontalLayout_3.addWidget(self.lineEdit_proxy)
459 | self.horizontalLayout_3.setStretch(0, 1)
460 | self.horizontalLayout_3.setStretch(1, 1)
461 | self.horizontalLayout_3.setStretch(2, 4)
462 | self.horizontalLayout_2.addWidget(self.widget_5)
463 | self.horizontalLayout_2.setStretch(0, 1)
464 | self.horizontalLayout_2.setStretch(1, 5)
465 | self.verticalLayout.addWidget(self.widget_proxy)
466 | self.verticalLayout.setStretch(0, 1)
467 | self.verticalLayout.setStretch(1, 1)
468 | self.verticalLayout.setStretch(2, 2)
469 | self.verticalLayout.setStretch(3, 1)
470 | self.verticalLayout.setStretch(4, 2)
471 | self.verticalLayout.setStretch(5, 1)
472 | self.gridLayout_3.addWidget(self.groupBox_config, 0, 0, 1, 1)
473 | self.plainTextEdit_log = QtWidgets.QPlainTextEdit(self.centralwidget)
474 | font = QtGui.QFont()
475 | font.setFamily("Ubuntu Mono")
476 | self.plainTextEdit_log.setFont(font)
477 | self.plainTextEdit_log.setReadOnly(True)
478 | self.plainTextEdit_log.setPlainText("")
479 | self.plainTextEdit_log.setTabStopWidth(4)
480 | self.plainTextEdit_log.setMaximumBlockCount(0)
481 | self.plainTextEdit_log.setObjectName("plainTextEdit_log")
482 | self.gridLayout_3.addWidget(self.plainTextEdit_log, 2, 0, 1, 2)
483 | self.gridLayout_3.setColumnStretch(0, 8)
484 | self.gridLayout_3.setColumnStretch(1, 2)
485 | self.gridLayout_3.setRowStretch(0, 7)
486 | self.gridLayout_3.setRowStretch(1, 3)
487 | self.gridLayout_3.setRowStretch(2, 4)
488 | MainWindow.setCentralWidget(self.centralwidget)
489 | self.menubar = QtWidgets.QMenuBar(MainWindow)
490 | self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 23))
491 | self.menubar.setObjectName("menubar")
492 | self.menuAbout = QtWidgets.QMenu(self.menubar)
493 | self.menuAbout.setObjectName("menuAbout")
494 | MainWindow.setMenuBar(self.menubar)
495 | self.statusbar = QtWidgets.QStatusBar(MainWindow)
496 | self.statusbar.setObjectName("statusbar")
497 | MainWindow.setStatusBar(self.statusbar)
498 | self.actionAbout = QtWidgets.QAction(MainWindow)
499 | self.actionAbout.setObjectName("actionAbout")
500 | self.menuAbout.addAction(self.actionAbout)
501 | self.menubar.addAction(self.menuAbout.menuAction())
502 | self.label.setBuddy(self.lineEdit_keywords)
503 | self.label_7.setBuddy(self.lineEdit_output)
504 | self.label_6.setBuddy(self.spinBox_max_number)
505 | self.label_5.setBuddy(self.spinBox_num_threads)
506 |
507 | self.retranslateUi(MainWindow)
508 | self.checkBox_from_file.clicked['bool'].connect(self.lineEdit_keywords.setDisabled) # type: ignore
509 | self.checkBox_from_file.clicked['bool'].connect(self.pushButton_load_file.setEnabled) # type: ignore
510 | self.checkBox_from_file.clicked['bool'].connect(self.lineEdit_path2file.setEnabled) # type: ignore
511 | self.checkBox_proxy.clicked['bool'].connect(self.widget_5.setEnabled) # type: ignore
512 | self.checkBox_proxy.clicked['bool'].connect(self.lineEdit_proxy.setFocus) # type: ignore
513 | self.radioButton_google.toggled['bool'].connect(self.checkBox_safe_mode.setEnabled) # type: ignore
514 | self.radioButton_bing.toggled['bool'].connect(self.checkBox_safe_mode.setChecked) # type: ignore
515 | self.radioButton_baidu.toggled['bool'].connect(self.checkBox_safe_mode.setChecked) # type: ignore
516 | self.radioButton_google.toggled['bool'].connect(self.checkBox_safe_mode.setChecked) # type: ignore
517 | self.checkBox_from_file.clicked['bool'].connect(self.pushButton_load_file.click) # type: ignore
518 | self.radioButton_google.toggled['bool'].connect(self.radioButton_api.setDisabled) # type: ignore
519 | self.radioButton_baidu.toggled['bool'].connect(self.radioButton_api.setChecked) # type: ignore
520 | self.radioButton_baidu.toggled['bool'].connect(self.radioButton_api.setEnabled) # type: ignore
521 | self.radioButton_google.toggled['bool'].connect(self.radioButton_chrome_headless.setChecked) # type: ignore
522 | self.radioButton_bing.toggled['bool'].connect(self.radioButton_api.setChecked) # type: ignore
523 | self.radioButton_bing.toggled['bool'].connect(self.radioButton_api.setEnabled) # type: ignore
524 | QtCore.QMetaObject.connectSlotsByName(MainWindow)
525 | MainWindow.setTabOrder(self.lineEdit_keywords, self.checkBox_from_file)
526 | MainWindow.setTabOrder(self.checkBox_from_file, self.lineEdit_path2file)
527 | MainWindow.setTabOrder(self.lineEdit_path2file, self.pushButton_load_file)
528 | MainWindow.setTabOrder(self.pushButton_load_file, self.lineEdit_output)
529 | MainWindow.setTabOrder(self.lineEdit_output, self.pushButton_output)
530 | MainWindow.setTabOrder(self.pushButton_output, self.checkBox_face_only)
531 | MainWindow.setTabOrder(self.checkBox_face_only, self.checkBox_safe_mode)
532 | MainWindow.setTabOrder(self.checkBox_safe_mode, self.spinBox_max_number)
533 | MainWindow.setTabOrder(self.spinBox_max_number, self.spinBox_num_threads)
534 | MainWindow.setTabOrder(self.spinBox_num_threads, self.checkBox_proxy)
535 | MainWindow.setTabOrder(self.checkBox_proxy, self.radioButton_http)
536 | MainWindow.setTabOrder(self.radioButton_http, self.radioButton_socks5)
537 | MainWindow.setTabOrder(self.radioButton_socks5, self.lineEdit_proxy)
538 | MainWindow.setTabOrder(self.lineEdit_proxy, self.pushButton_start)
539 | MainWindow.setTabOrder(self.pushButton_start, self.pushButton_cancel)
540 | MainWindow.setTabOrder(self.pushButton_cancel, self.radioButton_google)
541 | MainWindow.setTabOrder(self.radioButton_google, self.radioButton_bing)
542 | MainWindow.setTabOrder(self.radioButton_bing, self.radioButton_baidu)
543 | MainWindow.setTabOrder(self.radioButton_baidu, self.plainTextEdit_log)
544 |
545 | def retranslateUi(self, MainWindow):
546 | _translate = QtCore.QCoreApplication.translate
547 | MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
548 | self.groupBox_3.setTitle(_translate("MainWindow", "Progress"))
549 | self.label_4.setText(_translate("MainWindow", "Time Elapsed:"))
550 | self.label_time_elapsed.setText(_translate("MainWindow", "00:00:00"))
551 | self.label_2.setText(_translate("MainWindow", "Total Progress:"))
552 | self.label_3.setText(_translate("MainWindow", "Current Progress:"))
553 | self.groupBox_2.setTitle(_translate("MainWindow", "Control"))
554 | self.pushButton_start.setText(_translate("MainWindow", "Start"))
555 | self.pushButton_start.setShortcut(_translate("MainWindow", "Return"))
556 | self.pushButton_cancel.setText(_translate("MainWindow", "&Cancel"))
557 | self.groupBox_config.setTitle(_translate("MainWindow", "Config"))
558 | self.radioButton_google.setText(_translate("MainWindow", "&Google"))
559 | self.radioButton_bing.setText(_translate("MainWindow", "&Bing"))
560 | self.radioButton_baidu.setText(_translate("MainWindow", "B&aidu"))
561 | self.radioButton_chrome_headless.setText(_translate("MainWindow", "ChromeHeadless"))
562 | self.radioButton_chrome.setText(_translate("MainWindow", "Chrome"))
563 | self.radioButton_api.setText(_translate("MainWindow", "API"))
564 | self.label.setText(_translate("MainWindow", "&Keywords:"))
565 | self.lineEdit_keywords.setToolTip(_translate("MainWindow", "Input keywords, seperated by comma \", \""))
566 | self.lineEdit_keywords.setStatusTip(_translate("MainWindow", "Hint: e.g. To download image of Micheal Jordan and Yao Ming, input \"Micheal Jordan, Yao Ming\""))
567 | self.lineEdit_keywords.setPlaceholderText(_translate("MainWindow", "Seperates by comma ( , )"))
568 | self.checkBox_from_file.setText(_translate("MainWindow", "&Load File:"))
569 | self.lineEdit_path2file.setStatusTip(_translate("MainWindow", "Hint: Enter path to a text file. Each line of the file contains a group of keywords"))
570 | self.lineEdit_path2file.setPlaceholderText(_translate("MainWindow", "Path to file"))
571 | self.pushButton_load_file.setText(_translate("MainWindow", "..."))
572 | self.label_7.setText(_translate("MainWindow", "&Output:"))
573 | self.lineEdit_output.setToolTip(_translate("MainWindow", "Path to output directory."))
574 | self.lineEdit_output.setStatusTip(_translate("MainWindow", "Path to output directory."))
575 | self.lineEdit_output.setText(_translate("MainWindow", "./download_images"))
576 | self.lineEdit_output.setPlaceholderText(_translate("MainWindow", "Path to output directory."))
577 | self.pushButton_output.setText(_translate("MainWindow", "..."))
578 | self.checkBox_face_only.setText(_translate("MainWindow", "&Face Only"))
579 | self.checkBox_safe_mode.setText(_translate("MainWindow", "&Safe Mode"))
580 | self.label_6.setText(_translate("MainWindow", "Max &number\n"
581 | "per keywords"))
582 | self.label_5.setText(_translate("MainWindow", "&Threads:"))
583 | self.checkBox_proxy.setText(_translate("MainWindow", "&Proxy:"))
584 | self.radioButton_http.setText(_translate("MainWindow", "HTTP"))
585 | self.radioButton_socks5.setText(_translate("MainWindow", "Socks5"))
586 | self.lineEdit_proxy.setToolTip(_translate("MainWindow", "input ip:port"))
587 | self.lineEdit_proxy.setStatusTip(_translate("MainWindow", "xxx.xxx.xxx.xx:port"))
588 | self.lineEdit_proxy.setPlaceholderText(_translate("MainWindow", "xxx.xxx.xxx.xx:port"))
589 | self.menuAbout.setTitle(_translate("MainWindow", "Help"))
590 | self.actionAbout.setText(_translate("MainWindow", "About"))
591 |
--------------------------------------------------------------------------------
/utils.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # author: Yabin Zheng
3 | # Email: sczhengyabin@hotmail.com
4 |
5 | import chromedriver_autoinstaller
6 |
7 | def gen_valid_dir_name_for_keywords(keywords):
8 | keep = ["-", "_", "."]
9 | keywords = keywords.replace(" ", "_").replace(":", "-")
10 | return "".join(c for c in keywords if c.isalnum() or c in keep).rstrip()
11 |
12 |
13 | class AppConfig(object):
14 | def __init__(self):
15 | self.engine = "Google"
16 |
17 | self.driver = "chrome_headless"
18 |
19 | self.keywords = ""
20 |
21 | self.max_number = 0
22 |
23 | self.face_only = False
24 |
25 | self.safe_mode = False
26 |
27 | self.proxy_type = None
28 | self.proxy = None
29 |
30 | self.num_threads = 50
31 |
32 | self.output_dir = "./output"
33 |
34 | def to_command_paras(self):
35 | str_paras = ""
36 |
37 | str_paras += ' -e ' + self.engine
38 |
39 | str_paras += ' -d ' + self.driver
40 |
41 | str_paras += ' -n ' + str(self.max_number)
42 |
43 | str_paras += ' -j ' + str(self.num_threads)
44 |
45 | str_paras += ' -o "' + self.output_dir + '/' + \
46 | gen_valid_dir_name_for_keywords(self.keywords) + '"'
47 |
48 | if self.face_only:
49 | str_paras += ' -F '
50 |
51 | if self.safe_mode:
52 | str_paras += ' -S '
53 |
54 | if self.proxy_type == "http":
55 | str_paras += ' -ph "' + self.proxy + '"'
56 | elif self.proxy_type == "socks5":
57 | str_paras += ' -ps "' + self.proxy + '"'
58 |
59 | str_paras += ' "' + self.keywords + '"'
60 |
61 | return str_paras
62 |
63 |
64 | def gen_keywords_list_from_str(keywords_str, sep=","):
65 | return keywords_str.split(sep)
66 |
67 |
68 | def gen_keywords_list_from_file(filepath):
69 | with open(filepath, "r", encoding="utf-8") as f:
70 | return f.readlines()
71 |
72 | def resolve_dependencies(driver=str):
73 | if "chrome" in driver:
74 | print("Checking Google Chrome and chromedriver ...")
75 | driver_path = chromedriver_autoinstaller.install()
76 | if not driver_path:
77 | return False
78 | print("OK.")
79 | return True
80 |
--------------------------------------------------------------------------------