From 336792bc51cd3797d0050a43cd044382acfbc8d8 Mon Sep 17 00:00:00 2001 From: AdminWhaleFall Date: Fri, 8 Apr 2022 17:21:03 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20spider=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++ api.db | Bin 0 -> 81920 bytes config.py | 45 +++++++++++ hz-web.json | 57 ++++++++++++++ spider-api.py | 184 +++++++++++++++++++++++++++++++++++++++++++++ utils.py | 61 +++++++++++++++ 6 files changed, 548 insertions(+) create mode 100644 LICENSE create mode 100644 api.db create mode 100644 config.py create mode 100644 hz-web.json create mode 100644 spider-api.py create mode 100644 utils.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b77f30d --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022 WhaleFall + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/api.db b/api.db new file mode 100644 index 0000000000000000000000000000000000000000..1c23caeb39ad5f0cfcdadb09f73cff5b5556e394 GIT binary patch literal 81920 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 Za*OV~LW#AH@4W_Nmq0`6$vzTm{}&!1C2arz literal 0 HcmV?d00001 diff --git a/config.py b/config.py new file mode 100644 index 0000000..6fb346a --- /dev/null +++ b/config.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# @Time : 2022/1/11 1:46 +# @Author : WhaleFall +# @Site : +# @File : config.py +# @Description : 项目的配置文件 +import os +from pathlib import Path +import platform +import sys + + +class Config: + BASE_DIR = Path(__file__).resolve().parent # 项目绝对目录 + SQLITE_PATH = os.path.join(BASE_DIR, 'db', 'data.db') + # 默认UA + DEFAULT_UA = "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 " \ + "Safari/534.50" + LOGS_DIR = Path(BASE_DIR, 'logs') + if platform.system() == "Windows": + DATABASE_URI = 'sqlite:///' + SQLITE_PATH + else: + DATABASE_URI = 'sqlite:////' + SQLITE_PATH + + engine = create_engine(DATABASE_URI) # 数据库引擎 + + def __init__(self): + dirs = [Path(self.BASE_DIR, 'db'), self.LOGS_DIR] + for dir_ in dirs: + dir_.mkdir(exist_ok=True) + + +config = Config() # 实例化配置 + +log_config = { + # 添加接收器 + "handlers": [ + # 写入日志文件不必用颜色,rotation="1 day": 文件超过一天就会分割 + # run_{time:YYYY_M_D}.log: 日志格式 + {"sink": str(config.LOGS_DIR) + "/run_{time:YYYY_M_D}.log", "rotation": "1 day", "encoding": "utf-8", + "backtrace": True, "diagnose": True, "colorize": False, "level": "DEBUG"}, + # 标准输出流 + {"sink": sys.stdout, "backtrace": True, "diagnose": True, "colorize": True, "level": "DEBUG"}, + ], +} diff --git a/hz-web.json b/hz-web.json new file mode 100644 index 0000000..af406b9 --- /dev/null +++ b/hz-web.json @@ -0,0 +1,57 @@ +[ + { + "url": "https://duanxin.97sq.com/", + "key": "", + "title": "在线短信测压 97社区短信测压" + }, + { + "url": "https://www.yxdhma.cn/", + "key": "", + "title": "大树免费短信测压" + }, + { + "url": "http://107.173.149.61/index.php", + "key": "", + "title": "在线短信测压 智云短信轰炸" + }, + { + "url": "http://www.ono.plus/iaJIR00a0sqf/index.php", + "key": "", + "title": "在线短信测压" + }, + { + "url": "http://91zn.top/ylcs/", + "key": "", + "title": "免费短信测压" + }, + { + "url": "http://103.45.122.14/index.php", + "key": "", + "title": "Hello短信测压" + }, + { + "url": "https://ialtone.xyz/message/index.php", + "key": "", + "title": "ialtone的短信测压站" + }, + { + "url": "http://lzc.muigs.xyz/index2.php?", + "key": "", + "title": "短信测压-冷之晨" + }, + { + "url": "http://101.132.154.124:1200/index.php", + "key": "", + "title": "在线短信测压轰炸" + }, + { + "url": "https://ceya.kpxdr.com/index.php", + "key": "", + "title": "在线短信测压—云端轰炸" + }, + { + "url": "https://128.14.239.248/index.php", + "key": "", + "title": "在线短信测压 秋思短信轰炸" + } +] \ No newline at end of file diff --git a/spider-api.py b/spider-api.py new file mode 100644 index 0000000..f0adf0a --- /dev/null +++ b/spider-api.py @@ -0,0 +1,184 @@ +#!/usr/bin/python python3 +# coding=utf-8 +# 爬取轰炸平台接口 +from loguru import logger +import httpx +import requests +import re +from utils import Sql +import queue +import pathlib +import threading +import sys +import json +from prettytable import PrettyTable +import urllib3 +urllib3.disable_warnings() + +# logger config +logger.remove() +logger.add( + sink=sys.stdout, + format="{time:YYYY-MM-DD at HH:mm:ss} - {level} - {message}", + colorize=True, + backtrace=True +) + + +path = pathlib.Path(__file__).parent +header = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.9 Safari/537.36", +} + + +class SMS(object): + # 默认的请求密钥 + default_phone = "15019682928" + key_default = f"?hm={default_phone}&ok=" + + def __init__(self, website, key) -> None: + self.url = website + self.header = header + if key == "": + self.key = self.key_default + self.api_queue = queue.Queue() + self.db = Sql() + self.lock = threading.Lock() + self.ok_api = 0 + + def get_sms_api(self): + '''请求短信轰炸平台''' + with httpx.Client(verify=False) as ses: + ses.get(self.url, headers=self.header) + resp = ses.get(f"{self.url}{self.key}", headers=self.header) + + pat = re.compile(r"") + apis = pat.findall(resp.text) + assert not apis == [], "未找到任何接口!" + + logger.info("获取到的原始接口总数:%s" % (len(apis))) + + for api in apis: + + # 三重校验网址 + # 排除接口中没有电话号码的网址 + if self.default_phone not in api: + continue + + # 去除空白字符并替换默认手机号 + api = api.strip().replace(" ", "").replace( + self.default_phone, "[phone]") + + # 校验网址开头 + if not (api.startswith("https://") or api.startswith("http://")): + continue + + self.api_queue.put(api) + + logger.info("Put到队列的接口总数:%s" % (self.api_queue.qsize())) + self.size = self.api_queue.qsize() + + def check_theads(self): + '''多线程检查可用性''' + while not self.api_queue.empty(): + api = self.api_queue.get() + try: + with requests.get(api.replace("[phone]", self.default_phone), headers=self.header, timeout=8, verify=False) as resp: + if resp.status_code == 200: + with self.lock: + self.db.update(api) + + except Exception as e: + pass + finally: + self.api_queue.task_done() + + def main(self): + self.get_sms_api() + # 在此设置线程数 int 类型 + threads_count = 128 + threads = [ + threading.Thread(target=self.check_theads, + name=f"{i}", daemon=True) + for i in range(1, threads_count+1) + ] + for thread in threads: + thread.start() + logger.info("多线程校验进行中......(可能耗时比较长)") + from tqdm import tqdm + import time + with tqdm(total=self.size) as pbar: + while not self.api_queue.empty(): + pbar.update(self.size-self.api_queue.qsize()) + self.size = self.api_queue.qsize() + time.sleep(0.5) + self.api_queue.join() + logger.info(f"总接口数目(去重后):{len(self.db.select())}") + + +def test_api_web(url: str) -> tuple: + """check api web is ok? + :return: tuple + """ + if url is None: + return + with httpx.Client(headers=header, verify=False) as client: + try: + resp = client.get(url=url).text + title = re.findall('(.*?)', resp) + if title: + logger.info(f"{url} title:{title[0]}") + return (title[0], url) + except httpx.HTTPError as why: + logger.error(f"{url} 请求错误! {why}") + + return + + +def load_api_web(): + """从 json 文件加载轰炸网址.并测试! + :return: + """ + json_path = pathlib.Path(path, 'hz-web.json') + table = PrettyTable(["标题", "链接"]) + if not json_path.exists(): + logger.error(f"hz-web.json not exists in {str(json_path)}!") + return + j = json_path.read_text(encoding="utf8") + ok_web = [] + try: + webs = json.loads(j) + except json.decoder.JSONDecodeError as why: + logger.error(f"json syctax error! {why}") + return + + for web in webs: + result = test_api_web(web['url']) + if result: + table.add_row([result[0], result[1]]) + ok_web.append( + {"url": result[1], "key": web.get('key'), "title": result[0]}) + + logger.success(f"有效的轰炸网站:\n{table}") + if input(">>是否写入 hz-web.json?(Y/n)") == "Y": + with open(json_path, encoding="utf8", mode="w") as fp: + try: + json.dump(ok_web, fp, ensure_ascii=False) + logger.success("save hz-web.json success!") + except Exception as why: + logger.error(f"write hz-web.json error {why}") + return ok_web + + +def main(): + websites = load_api_web() + for website in websites: + logger.info(f"正在爬取:{website['url']}") + try: + sms = SMS(website=website['url'], key=website['key']).main() + except Exception as why: + logger.critical(f"爬取:{website['url']} 出错:{why}") + + +if __name__ == '__main__': + main() diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..73a2184 --- /dev/null +++ b/utils.py @@ -0,0 +1,61 @@ +# coding=utf-8 +# 读写数据库模块 +import sqlite3 +from pathlib import Path + + +class Sql(object): + def __init__(self) -> None: + '''初始化数据库''' + # 数据库路径 + db_path = Path.cwd().joinpath("api.db") + # 连接数据库,不检查是否在同一个路径. + self.client = sqlite3.connect( + db_path, timeout=6, check_same_thread=False) + self.cursor = self.client.cursor() + self.newTable() + + def newTable(self): + '''初始化表结构''' + sql = ''' +CREATE TABLE IF NOT EXISTS API200 ( + id INT NULL, + url TEXT NOT NULL, + primary key (url) +); + ''' + self.cursor.execute(sql) + self.client.commit() + + def update(self, url): + '''插入数据''' + sql = ''' + INSERT INTO API200 (ID,url) VALUES (null,?) + ''' + try: + self.cursor.execute(sql, (url,)) + self.client.commit() + return True + except sqlite3.IntegrityError: + # print(f"{url} 数据重复!") + return False + + def select(self) -> list: + '''获取所有接口''' + sql = ''' + SELECT url FROM API200; + ''' + try: + self.cursor.execute(sql) + result = self.cursor.fetchall() + urls = [] + for url in result: + urls.append(url[0]) + return urls + except Exception as e: + print('读取出现错误!', e) + + def __del__(self) -> None: + '''对象被删除时执行的函数''' + print(f"共改变{self.client.total_changes}条数据!,正在关闭数据库连接......") + self.client.close()