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 0000000..1c23cae Binary files /dev/null and b/api.db differ 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()