QQwry到Sqlite3(续)

本文继续探讨如何将QQwry的数据转换并导入到Sqlite3数据库中,重点介绍处理integer、null值以及建立表格的详细步骤,同时使用Python作为工具进行操作。
下面是用于查询转成sqlite3的qqwry的python代码

'''
Created on 2010-5-4

@author: Ben
@mail: ben.pang.china@gmail.com
'''

import os, sys, re
import sqlite3
from converter import b2I, dbname

def int2Ip(i):
    """IP字符串转换成IP对象"""
    i = int(i)
    return IP( "{}.{}.{}.{}".format( i >> 24, i  >> 16 & 0xFF, i  >> 8 & 0xFF,  i & 0xFF ) )

def ip2Int(ip):
    """IP字符串转换成Integer"""
    if ipm.match(ip):
        return b2I( reversed(list( map( int, ip.split(".") ) )) )

# local variables
ipm = re.compile( "^((2[0-4]d|25[0-5]|[01]?dd?).){3}(2[0-4]d|25[0-5]|[01]?dd?)$" )
conn = None

def init():
    global conn
    sqlite3.register_converter("ip", int2Ip)
    conn = sqlite3.connect(dbname, detect_types = sqlite3.PARSE_COLNAMES);
    
class IP:
    def __init__(self, ip):
        """IP类,存放IP地址,并验证IP字符串是否有效"""
        if not ipm.match(ip):
            raise ValueError( "invalid ip value:" + ip )
        self.ip = ip
    
    def __str__(self):
        return self.ip

if __name__ == '__main__':
    if os.path.exists(dbname) and len(sys.argv) == 2 and ipm.match(sys.argv[1]):
        init()
        cur = conn.cursor()
        cur.execute("""
        SELECT start_ip as "s [ip]",
        end_ip as "e [ip]",
        country.name,
        area.name
        FROM ipaddr,country,area WHERE (? BETWEEN start_ip AND end_ip) AND cid = country.id AND aid = area.id;
        """, (ip2Int(sys.argv[1]), ) )
        
        for r in cur:
            print("""
            start ip: {} ~ {}
            {}
            """.format(*r))
        
        conn.close()
    else:
        print("not found ipdb for" + dbname, file = sys.stderr )

 

很不幸,经过尝试转成sqlite3的QQwry的压缩率并没有我想的那么高,整个库文件有31M,是QQwry原格式的3倍.

 

下面是SQL脚本

CREATE TABLE IF NOT EXISTS country (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL UNIQUE
);

CREATE TABLE IF NOT EXISTS area (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL UNIQUE
);

CREATE TABLE IF NOT EXISTS ipaddr (
    start_ip INTEGER PRIMARK KEY,
    end_ip INTEGER NOT NULL UNIQUE,
    cid INTEGER NOT NULL,
    aid INTEGER DEFAULT NULL
);

CREATE INDEX IF NOT EXISTS ipIdx ON ipaddr (start_ip, end_ip)

 

因为在SQLITE3里把IP转成integer了,所以可以看到上面的代码中注册了一个convertor用于转换,如果觉得不方便还可以注册funtion viewIP,然后create view,使用注册的函数显示IP,这会增加开销,但可以满足使用习惯
像这样

 

create view ipview as SELECT viewIP(start_ip),... FROM ipaddr....


OK.


app.py -- coding: utf-8 -- from flask import Flask, jsonify, request, render_template, redirect, url_for from flask_login import LoginManager,current_user from flask_wtf.csrf import CSRFProtect import logging import os import sys from datetime import datetime import warnings import urllib3 from urllib3.exceptions import InsecureRequestWarning from sqlalchemy import text import time import threading 禁用SSL警告 warnings.filterwarnings("ignore", category=InsecureRequestWarning) urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 添加当前目录到Python路径 current_dir = os.path.dirname(os.path.abspath(file)) sys.path.insert(0,current_dir) from config import config, DevelopmentConfig from database.models import db,User, UserProxy, Proxy, SystemConfig from core.scheduler import SchedulerManager from web.auth import auth_bp from web.views import web_bp 配置日志 - 简化日志输出 logging.basicConfig( level=logging.INFO, format=&#39;%(asctime)s - %(levelname)s - %(message)s&#39;, handlers=[logging.StreamHandler(sys.stdout)] ) logger= logging.getLogger(name) login_manager = LoginManager() login_manager.login_view= &#39;auth.login&#39; login_manager.login_message= &#39;请先登录以访问此页面&#39; csrf= CSRFProtect() 全局调度器实例 scheduler_manager = None _app_initialized= False _app_initializing= False _scheduler_started= False def get_database_uri(): """获取数据库URI字符串""" try: config_instance = DevelopmentConfig() return config_instance.SQLALCHEMY_DATABASE_URI except Exception as e: logger.error(f"获取数据库URI失败: {e}") return f"sqlite:///{os.path.join(current_dir, &#39;proxy_pool.db&#39;)}" def test_database_connection(): """测试数据库连接""" try: db.session.execute(text(&#39;SELECT 1&#39;)) return True except Exception as e: logger.warning(f"数据库连接异常: {e}") return False def check_first_run(app): """检查是否是首次运行""" try: db_uri = app.config.get(&#39;SQLALCHEMY_DATABASE_URI&#39;, &#39;&#39;) def initialize_scheduler(app): """初始化并启动调度器""" global scheduler_manager, _scheduler_started def initialize_application(app): """应用初始化函数""" global _app_initialized, _app_initializing def create_app(config_name=&#39;default&#39;): app = Flask(name) 命令行运行 - 修改启动方式 if name == &#39;main&#39;: app = create_app() utils/helpers.py import re from datetime import datetime,timedelta from urllib.parse import urlparse import ipaddress import os import logging from sqlalchemy import inspect,text logger = logging.getLogger(name) import pymysql import sqlite3 import logging from sqlalchemy import create_engine from sqlalchemy.exc import OperationalError 添加纯真IP查询库 import qqwry logger = logging.getLogger(name) def test_database_connection(db_config): """ 测试数据库连接是否可用 db_config: 数据库配置字典 返回: (成功与否, 错误信息) """ try: db_type = db_config.get(&#39;DB_TYPE&#39;, &#39;sqlite&#39;) def is_valid_ip(ip): """验证IP地址是否有效""" try: ipaddress.ip_address(ip) return True except ValueError: return False def is_valid_port(port): """验证端口是否有效""" try: port = int(port) return 1 <= port <= 65535 except ValueError: return False def is_valid_protocol(protocol): """验证协议是否有效""" return protocol.lower() in [&#39;http&#39;, &#39;https&#39;, &#39;socks4&#39;, &#39;socks5&#39;] def parse_proxy_string(proxy_str, default_protocol=&#39;http&#39;): """解析代理字符串""" if not proxy_str or not isinstance(proxy_str, str): return None def format_proxy_string(proxy_dict): """格式化代理为字符串""" if not proxy_dict: return "" 初始化纯真IP查询对象(单例模式) _qqwry_reader = None def get_qqwry_reader(): """获取纯真IP查询读取器""" global _qqwry_reader if _qqwry_reader is None: try: # 自动下载或使用本地纯真IP数据库 _qqwry_reader = qqwry.QQwry() _qqwry_reader.load_file(&#39;qqwry.dat&#39;) # 默认使用当前目录的qqwry.dat文件 except Exception as e: logger.error(f"初始化纯真IP数据库失败: {e}") return None return _qqwry_reader def get_location_from_ip(ip): """根据IP获取地理位置(使用纯真IP查询)""" try: # 验证IP地址有效性 if not is_valid_ip(ip): return "无效IP" def calculate_next_run(interval_seconds): """计算下一次运行时间""" return datetime.now() + timedelta(seconds=interval_seconds) def format_timedelta(delta): """格式化时间间隔""" if not delta: return "从未" def humanize_time(dt): """人性化时间显示""" if not dt: return "从未" web/views.py from functools import wraps from flask import Blueprint,render_template, jsonify, request, flash, redirect, url_for, current_app from flask_login import login_required,current_user, login_user from datetime import datetime,timedelta import json import threading from time import sleep import logging import os import subprocess from sqlalchemy import inspect from database.models import db, Proxy, User, UserProxy, CrawlerRule, SystemConfig from web.forms import CrawlerRuleForm,UserForm, SystemConfigForm, ImportForm, FirstRunForm, DatabaseMigrationForm from core.crawler import ProxyCrawler from core.validator import ProxyValidator from utils.importer import ProxyImporter web_bp = Blueprint(&#39;web&#39;, name) logger= logging.getLogger(name) 添加模板过滤器 @web_bp.app_template_filter(&#39;safe_date&#39;) def safe_date_filter(value,format=&#39;%Y-%m-%d %H:%M&#39;): """安全的日期格式化过滤器""" if value is None: return "从未" try: if hasattr(value, &#39;strftime&#39;): return value.strftime(format) elif isinstance(value, str): # 尝试解析字符串日期 try: if &#39;T&#39; in value: dt = datetime.fromisoformat(value.replace(&#39;Z&#39;, &#39;+00:00&#39;)) else: dt = datetime.strptime(value, &#39;%Y-%m-%d %H:%M:%S&#39;) return dt.strftime(format) except (ValueError, TypeError): return value else: return str(value) except (AttributeError, ValueError): return "无效日期" 检查是否需要首次运行 web/views.py 中的 check_first_run 函数需要修复 def check_first_run(): """更健壮的首次运行检查""" try: # 检查数据库连接是否正常 from sqlalchemy import text db.session.execute(text(&#39;SELECT 1&#39;)) 管理员权限装饰器 def admin_required(f): @wraps(f) def decorated_function(args, **kwargs): if not current_user.is_authenticated: flash(&#39;请先登录&#39;, &#39;danger&#39;) return redirect(url_for(&#39;auth.login&#39;)) if not current_user.is_admin: flash(&#39;需要管理员权限&#39;, &#39;danger&#39;) return redirect(url_for(&#39;web.dashboard&#39;)) return f(args, **kwargs) return decorated_function 全局变量用于跟踪验证进度 validation_progress = { &#39;running&#39;: False, &#39;total&#39;: 0, &#39;completed&#39;: 0, &#39;message&#39;: &#39;&#39; } 添加首次运行检查中间件 @web_bp.before_request def before_request(): # 排除静态文件和首次运行路由 if request.endpoint and ( request.endpoint.startswith(&#39;static&#39;) or request.endpoint in [&#39;web.first_run&#39;, &#39;web.first_run_setup&#39;] ): return web/views.py 中的首次运行路由 @web_bp.route(&#39;/first-run&#39;, methods=[&#39;GET&#39;]) def first_run(): # 使用新的检查方法 from utils.helpers import check_first_run @web_bp.route(&#39;/first-run/setup&#39;, methods=[&#39;POST&#39;]) def first_run_setup(): from utils.helpers import check_first_run 数据库迁移页 @web_bp.route(&#39;/database-migration&#39;, methods=[&#39;GET&#39;, &#39;POST&#39;]) @login_required @admin_required def database_migration(): migration_form = DatabaseMigrationForm() 修改设置页面路由,添加数据库迁移选项 @web_bp.route(&#39;/settings&#39;, methods=[&#39;GET&#39;, &#39;POST&#39;]) @login_required @admin_required def settings(): form = SystemConfigForm() @web_bp.route(&#39;/&#39;) @login_required def dashboard(): from database.models import Proxy, User, CrawlerRule, SystemConfig @web_bp.route(&#39;/proxies&#39;) @login_required def proxies(): page = request.args.get(&#39;page&#39;, 1, type=int) per_page = 20 @web_bp.route(&#39;/test-proxy/&#39;) @login_required def test_proxy(proxy_id): """测试单个代理""" proxy = Proxy.query.get_or_404(proxy_id) @web_bp.route(&#39;/edit-proxy/&#39;, methods=[&#39;POST&#39;]) @login_required @admin_required def edit_proxy(proxy_id): """编辑代理信息""" proxy = Proxy.query.get_or_404(proxy_id) @web_bp.route(&#39;/validate-all-proxies&#39;) @login_required @admin_required def validate_all_proxies(): """后台验证所有代理""" global validation_progress @web_bp.route(&#39;/get-validation-progress&#39;) @login_required def get_validation_progress(): """获取验证进度""" global validation_progress return jsonify(validation_progress) @web_bp.route(&#39;/my-proxies&#39;) @login_required def my_proxies(): user_proxies = current_user.proxies.join(Proxy).filter( UserProxy.is_active == True ).order_by(UserProxy.assigned_at.desc()).all() @web_bp.route(&#39;/get-proxy&#39;) @login_required def get_proxy(): """获取代理 - 添加到期时间检查""" # 检查用户是否到期 if not current_user.can_access_system(): return jsonify({&#39;error&#39;: &#39;您的账户已到期或已被禁用&#39;}), 403 @web_bp.route(&#39;/proxy-info/&#39;) @login_required @admin_required def proxy_info(proxy_id): """获取代理信息""" proxy = Proxy.query.get_or_404(proxy_id) return jsonify(proxy.to_dict()) @web_bp.route(&#39;/crawlers&#39;, methods=[&#39;GET&#39;, &#39;POST&#39;]) @login_required @admin_required def crawlers(): form = CrawlerRuleForm() if form.validate_on_submit(): rule = CrawlerRule( name=form.name.data, url=form.url.data, pattern_type=form.pattern_type.data, pattern=form.pattern.data, protocol=form.protocol.data, interval=form.interval.data, is_active=form.is_active.data ) db.session.add(rule) db.session.commit() flash(&#39;Crawler rule added successfully&#39;) return redirect(url_for(&#39;web.crawlers&#39;)) @web_bp.route(&#39;/run-crawler/&#39;) @login_required @admin_required def run_crawler(rule_id): rule = CrawlerRule.query.get_or_404(rule_id) crawler = ProxyCrawler() proxies = crawler.crawl_rule(rule) new_count = crawler.save_proxies(proxies) @web_bp.route(&#39;/users&#39;, methods=[&#39;GET&#39;, &#39;POST&#39;]) @login_required @admin_required def users(): form = UserForm() @web_bp.route(&#39;/edit-user/&#39;) @login_required @admin_required def edit_user(user_id): """获取用户信息用于编辑""" user = User.query.get_or_404(user_id) @web_bp.route(&#39;/import&#39;, methods=[&#39;GET&#39;, &#39;POST&#39;]) @login_required @admin_required def import_proxies(): form = ImportForm() importer = ProxyImporter() @web_bp.route(&#39;/api/docs&#39;) @login_required def api_docs(): return render_template(&#39;api_docs.html&#39;) @web_bp.route(&#39;/replace-proxy/&#39;) @login_required def replace_proxy(proxy_id): """更换用户代理""" user_proxy = UserProxy.query.get_or_404(proxy_id) @web_bp.route(&#39;/release-proxy/&#39;) @login_required def release_proxy(proxy_id): """释放用户代理""" user_proxy = UserProxy.query.get_or_404(proxy_id) @web_bp.route(&#39;/delete-user/&#39;, methods=[&#39;DELETE&#39;]) @login_required @admin_required def delete_user(user_id): """删除用户""" if user_id == current_user.id: return jsonify({&#39;error&#39;: &#39;不能删除自己&#39;}), 400 @web_bp.route(&#39;/reset-api-key/&#39;, methods=[&#39;POST&#39;]) @login_required @admin_required def reset_api_key(user_id): """重置用户API Key""" user = User.query.get_or_404(user_id) new_key = user.generate_api_key() db.session.commit() @web_bp.route(&#39;/users/add&#39;, methods=[&#39;POST&#39;]) @login_required @admin_required def add_user(): """添加新用户""" form = UserForm() if form.validate_on_submit(): try: # 检查用户名是否已存在 existing_user = User.query.filter_by(username=form.username.data).first() if existing_user: flash(&#39;用户名已存在&#39;, &#39;danger&#39;) return redirect(url_for(&#39;web.users&#39;)) @web_bp.route(&#39;/users/edit&#39;, methods=[&#39;POST&#39;]) @login_required @admin_required def edit_user_post(): """编辑用户信息""" form = UserForm() user_id = request.form.get(&#39;user_id&#39;) @web_bp.route(&#39;/users//edit&#39;) @login_required @admin_required def get_user_info(user_id): """获取用户信息用于编辑(API接口)""" user = User.query.get_or_404(user_id) @web_bp.route(&#39;/clear-invalid-proxies&#39;, methods=[&#39;POST&#39;]) @login_required @admin_required def clear_invalid_proxies(): """清理无效代理""" try: # 删除无效代理 invalid_proxies = Proxy.query.filter_by(is_valid=False).all() deleted_count = len(invalid_proxies) @web_bp.route(&#39;/test-crawler-rule&#39;, methods=[&#39;POST&#39;]) @login_required @admin_required def test_crawler_rule(): """测试抓取规则""" try: # 检查请求内容类型 if not request.is_json: return jsonify({ &#39;success&#39;: False, &#39;message&#39;: &#39;请求必须是JSON格式&#39;, &#39;proxies&#39;: [], &#39;html_content&#39;: &#39;&#39;, &#39;element_count&#39;: 0, &#39;valid_count&#39;: 0, &#39;raw_elements&#39;: [] }), 400 @web_bp.route(&#39;/crawler-detail/.json&#39;) @login_required @admin_required def crawler_detail_json(rule_id): """获取规则详情(JSON格式)""" rule = CrawlerRule.query.get_or_404(rule_id) return jsonify(rule.to_dict()) @web_bp.route(&#39;/crawler/&#39;) @login_required @admin_required def crawler_detail(rule_id): """查看抓取规则详情页面""" rule = CrawlerRule.query.get_or_404(rule_id) return render_template(&#39;crawler_detail.html&#39;, rule=rule) @web_bp.route(&#39;/toggle-crawler/&#39;) @login_required @admin_required def toggle_crawler(rule_id): """切换抓取规则状态""" rule = CrawlerRule.query.get_or_404(rule_id) rule.is_active = not rule.is_active db.session.commit() return jsonify({&#39;success&#39;: True}) @web_bp.route(&#39;/edit-crawler/&#39;, methods=[&#39;GET&#39;, &#39;POST&#39;]) @login_required @admin_required def edit_crawler(rule_id): """编辑抓取规则""" rule = CrawlerRule.query.get_or_404(rule_id) form = CrawlerRuleForm(obj=rule) @web_bp.route(&#39;/delete-crawler/&#39;, methods=[&#39;DELETE&#39;]) @login_required @admin_required def delete_crawler(rule_id): """删除抓取规则""" try: rule = CrawlerRule.query.get_or_404(rule_id) db.session.delete(rule) db.session.commit() web/forms.py from flask_wtf import FlaskForm from wtforms import StringField,PasswordField, BooleanField, IntegerField, SelectField, TextAreaField, SubmitField from wtforms.validators import DataRequired,Email, Length, EqualTo, ValidationError, URL ,Optional from database.models import User 添加首次运行表单 class FirstRunForm(FlaskForm): db_type = SelectField(&#39;数据库类型&#39;, choices=[ (&#39;sqlite&#39;, &#39;SQLite&#39;), (&#39;mysql&#39;, &#39;MySQL&#39;) ], default=&#39;sqlite&#39;, validators=[DataRequired()]) db_host = StringField(&#39;数据库主机&#39;, default=&#39;localhost&#39;, validators=[Optional()]) db_port = StringField(&#39;数据库端口&#39;, default=&#39;3306&#39;, validators=[Optional()]) db_name = StringField(&#39;数据库名称&#39;, default=&#39;proxy_pool&#39;, validators=[Optional()]) db_user = StringField(&#39;用户名&#39;, default=&#39;root&#39;, validators=[Optional()]) db_password = PasswordField(&#39;密码&#39;, validators=[Optional()]) admin_username = StringField(&#39;管理员用户名&#39;, validators=[DataRequired(), Length(1, 64)]) admin_password = PasswordField(&#39;管理员密码&#39;, validators=[DataRequired(), Length(6, 128)]) admin_password2 = PasswordField(&#39;确认密码&#39;, validators=[DataRequired(), EqualTo(&#39;admin_password&#39;)]) submit = SubmitField(&#39;保存配置&#39;) 添加数据库迁移表单 class DatabaseMigrationForm(FlaskForm): db_type = SelectField(&#39;目标数据库类型&#39;, choices=[ (&#39;sqlite&#39;, &#39;SQLite&#39;), (&#39;mysql&#39;, &#39;MySQL&#39;) ], validators=[DataRequired()]) db_host = StringField(&#39;数据库主机&#39;, default=&#39;localhost&#39;, validators=[Optional()]) db_port = StringField(&#39;数据库端口&#39;, default=&#39;3306&#39;, validators=[Optional()]) db_name = StringField(&#39;数据库名称&#39;, default=&#39;proxy_pool&#39;, validators=[Optional()]) db_user = StringField(&#39;用户名&#39;, default=&#39;root&#39;, validators=[Optional()]) db_password = PasswordField(&#39;密码&#39;, validators=[Optional()]) submit = SubmitField(&#39;开始迁移&#39;) class LoginForm(FlaskForm): username = StringField(&#39;用户名&#39;, validators=[DataRequired(), Length(1, 64)]) password = PasswordField(&#39;密码&#39;, validators=[DataRequired()]) remember_me = BooleanField(&#39;记住我&#39;) submit = SubmitField(&#39;登录&#39;) class RegistrationForm(FlaskForm): username = StringField(&#39;用户名&#39;, validators=[DataRequired(), Length(1, 64)]) email = StringField(&#39;邮箱&#39;, validators=[DataRequired(), Email()]) password = PasswordField(&#39;密码&#39;, validators=[DataRequired(), Length(6, 128)]) password2 = PasswordField(&#39;确认密码&#39;, validators=[DataRequired(), EqualTo(&#39;password&#39;)]) submit = SubmitField(&#39;注册&#39;) class CrawlerRuleForm(FlaskForm): name = StringField(&#39;规则名称&#39;, validators=[DataRequired(), Length(1, 100)]) url = StringField(&#39;目标URL&#39;, validators=[DataRequired(), URL(), Length(1, 500)]) pattern_type = SelectField(&#39;匹配类型&#39;, choices=[ (&#39;css&#39;, &#39;CSS选择器&#39;), (&#39;xpath&#39;, &#39;XPath&#39;), (&#39;regex&#39;, &#39;正则表达式&#39;) ], validators=[DataRequired()]) pattern = TextAreaField(&#39;主匹配模式&#39;, validators=[DataRequired()]) class UserForm(FlaskForm): username = StringField(&#39;用户名&#39;, validators=[DataRequired(), Length(1, 64)]) email = StringField(&#39;邮箱&#39;, validators=[DataRequired(), Email()]) password = PasswordField(&#39;密码&#39;, validators=[Length(6, 128)]) max_proxies = IntegerField(&#39;最大代理数&#39;, default=100, validators=[DataRequired()]) rate_limit = IntegerField(&#39;速率限制(次/分钟)&#39;, default=60, validators=[DataRequired()]) expires_at = StringField(&#39;到期时间&#39;, render_kw={&#39;placeholder&#39;: &#39;YYYY-MM-DD HH:MM:SS 或留空为永久&#39;}) is_active = BooleanField(&#39;账户激活&#39;, default=True) is_admin = BooleanField(&#39;管理员权限&#39;) submit = SubmitField(&#39;保存用户&#39;) class SystemConfigForm(FlaskForm): crawl_interval = IntegerField(&#39;抓取间隔()&#39;, default=3600, validators=[DataRequired()]) validate_interval = IntegerField(&#39;验证间隔()&#39;, default=300, validators=[DataRequired()]) validate_timeout = IntegerField(&#39;验证超时()&#39;, default=10, validators=[DataRequired()]) validate_url = StringField(&#39;验证URL&#39;, default=&#39;http://httpbin.org/ip&#39;, validators=[DataRequired(), URL()]) class ImportForm(FlaskForm): import_type = SelectField(&#39;导入类型&#39;, choices=[ (&#39;text&#39;, &#39;文本导入&#39;), (&#39;api&#39;, &#39;API导入&#39;) ], validators=[DataRequired()]) protocol = SelectField(&#39;默认协议&#39;, choices=[ (&#39;http&#39;, &#39;HTTP&#39;), (&#39;https&#39;, &#39;HTTPS&#39;), (&#39;socks4&#39;, &#39;SOCKS4&#39;), (&#39;socks5&#39;, &#39;SOCKS5&#39;) ], default=&#39;http&#39;) text = TextAreaField(&#39;代理文本&#39;, render_kw={&#39;placeholder&#39;: &#39;每行一个代理,格式: 协议://用户:密码@IP:端口 或 IP:端口&#39;}) api_url = StringField(&#39;API地址&#39;, render_kw={&#39;placeholder&#39;: &#39;请输入API URL&#39;}) pattern = StringField(&#39;匹配模式&#39;, render_kw={&#39;placeholder&#39;: &#39;正则表达式模式&#39;}) submit = SubmitField(&#39;导入代理&#39;) class ClearProxiesForm(FlaskForm): submit = SubmitField(&#39;清理无效代理&#39;) app.py 首次运行不对了,首次运行网页跳转安装配置数据库和管理用户,修复后完整的代码
最新发布
09-22
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值