├── .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 | [![996.icu](https://img.shields.io/badge/link-996.icu-red.svg)](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 | ![GUI](/GUI.png) 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 | [![Star History Chart](https://api.star-history.com/svg?repos=QianyanTech/Image-Downloader&type=Date)](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 | [![996.icu](https://img.shields.io/badge/link-996.icu-red.svg)](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 | ![GUI](/GUI.png) 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 | 892 | 893 | 894 | 0 895 | 0 896 | 800 897 | 23 898 | 899 | 900 | 901 | 902 | Help 903 | 904 | 905 | 906 | 907 | 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 | --------------------------------------------------------------------------------