banner
肥皂的小屋

肥皂的小屋

github
steam
bilibili
douban
tg_channel

ローカルIPプロキシプールの構築(完結)

事情の発端#

代行業務 - コンピュータのローカル IP プロキシプールの構築

最終的に実現したい効果:GUIクライアントを設計し、プロキシを簡単に取得し、ランダムにプロキシを使用し、プロキシを切り替え、プロキシをクリアするなど、クリック一つでipを変更できるようにする。

素材の取得:『多スレッドで西刺の高匿名プロキシを取得し、可用性を検証する』という記事をPython ディレクトリで確認する。

西刺プロキシの py コードの書き換え#

3 ヶ月前に書いたコードが自分でも受け入れられないことに気づき、以下のようにまとめました(まとめられないプログラマーは良いセキュリティ初心者ではない):

問題改善方法
不適切なプロセスライブラリ Pool ().apply_async を使用したため、マルチプロセス時に保存されたリストを保持できず、ファイルの読み書き方式を採用したため、コードが非常に雑然としたプロセスとスレッドの適用性を再理解し、マルチスレッドキューを採用しました。
大量の I/O 操作により異なる環境での適応問題MongoDB データベースを使用して保存する
コードの再利用性が低いクラス内でクローラー操作とデータベース挿入操作を分け、データベース挿入関数を再利用可能にし、コードを大幅に簡素化しました。
コード例外を処理していないエラーが発生しやすい場所にコード例外処理 try except を追加しました。

参考チュートリアル:

全コード:

#!/usr/bin/python
# -*- coding: utf-8 -*-
'''
@author: soapffz
@function: 多スレッドで西刺の高匿名プロキシを取得し、mongodbデータベースに保存する
@time: 2019-04-20
'''
import pymongo  # mongodbデータベース操作
from threading import Thread  # マルチスレッド
from fake_useragent import UserAgent  # 偽のuseragent
import requests  # サイトへのリクエスト
from lxml import etree  # サイトの解析
import re  # re解析ライブラリ
import telnetlib  # telnet接続でプロキシの有効性をテスト
import timeit  # 時間計測


class multi_threaded_crawl_proxies(object):
    def __init__(self):
        try:
            # mongodbに接続し、接続オブジェクトを取得
            client = pymongo.MongoClient('mongodb://localhost:27017/')
            self.db = client.proxies  # proxiesデータベースを指定、このクラスはこのデータベースを使用
            print("データベース接続成功!")
        except Exception as e:
            print("データベース接続失敗:{}".format(e))
        self.ua = UserAgent()  # User-Agentを生成するために使用
        self.crawl_progress()

    def crawl_progress(self):
        # クローリング操作
        try:
            # 複数のクローリング関数の起動関数をここに追加できます
            self.xici_nn_proxy_start()
        except Exception as e:
            print("プログラム実行エラー:{}\nプログラム終了!".format(e))
            exit(0)

    def xici_nn_proxy_start(self):
        xici_t_cw_l = []  # クローリングスレッドリスト
        self.xici_proxies_l = []  # 検証済みのプロキシ辞書を保存するためのリスト、辞書にはip、ポート、ipタイプ、住所が含まれる
        for i in range(1, 21):  # 20ページのプロキシをクローリング
            t = Thread(target=self.xici_nn_proxy, args=(i,))
            xici_t_cw_l.append(t)  # スレッドをスレッドリストに追加
            t.start()
        for t in xici_t_cw_l:  # すべてのスレッドが完了するまでメインスレッドを待機
            t.join()
        self.db_insert(self.xici_proxies_l, "xici")  # データベースに挿入

    def xici_nn_proxy(self, page):
        # 西刺プロキシクローリング関数
        url = "https://www.xicidaili.com/nn/{}".format(page)
        # ここでuser-agentを加えないと503のステータスコードが返される
        req = requests.get(url, headers={"User-Agent": self.ua.random})
        if req.status_code != 200:
            print("ipが封鎖されました!このページのクローリングに失敗しました!")
            exit(0)
        else:
            print("第{}ページの内容をクローリング中...".format(page))
            content = req.content.decode("utf-8")
            tree = etree.HTML(content)
            # xpathを使用して総ip_listを取得
            tr_nodes = tree.xpath('.//table[@id="ip_list"]/tr')[1:]
            for tr_node in tr_nodes:
                td_nodes = tr_node.xpath('./td')  # xpathを使用して単一ipのタグを取得
                speed = int(re.split(r":|%", td_nodes[6].xpath(
                    './div/div/@style')[0])[1])  # 速度の値を取得
                conn_time = int(re.split(r":|%", td_nodes[7].xpath(
                    './div/div/@style')[0])[1])  # 接続時間の値を取得
                if(speed <= 85 | conn_time <= 85):  # 速度と接続時間がどちらも理想的でない場合、このプロキシをスキップ
                    continue
                ip = td_nodes[1].text
                port = td_nodes[2].text
                ip_type = td_nodes[5].text.lower()
                td_address = td_nodes[3].xpath("a/text()")
                address = 'None'
                if td_address:  # 一部の住所が空の場合、デフォルトで空にし、取得できた場合は対応する住所に設定
                    address = td_address[0]
                proxy = "{}:{}".format(ip, port)
                try:
                    # telnetで接続して、接続できればプロキシが使用可能
                    telnetlib.Telnet(ip, port, timeout=1)
                except:
                    pass
                else:
                    self.xici_proxies_l.append(
                        {"ip": ip, "port": port, "ip_type": ip_type, "address": address})

    def db_insert(self, proxies_list, collection_name):
        if proxies_list:
            # 渡されたリストが空の場合は終了
            collection = self.db['{}'.format(collection_name)]  # コレクションを選択、存在しない場合は自動的に作成
            collection.insert_many(proxies_list)  # 辞書のリストを要素として挿入
        else:
            print("プロキシリストが空です!\nプログラム終了!")
            exit(0)


if __name__ == "__main__":
    start_time = timeit.default_timer()
    multi_threaded_crawl_proxies()
    end_time = timeit.default_timer()
    print("プログラム実行終了、総時間:{}".format(end_time-start_time))

効果は以下の通り:

image

image


[19-04-26 更新]

レジストリパラメータの変更#

プロキシができたので、Windows のローカルプロキシを変更する必要があります。調べたところ、以下のことがわかりました:

  • winIEブラウザのプロキシ設定を通じて外部ネットワークにアクセスできます。
  • ネット上のほとんどの記事で紹介されている IE プロキシの位置は
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings
  • しかし、参考記事 1 の著者によると、実際に変更すべき場所は
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Connections

つまり、上記の位置にあるConnections項目で、IE プロキシに127.0.0.1:80のプロキシを設定します:

image

プロキシを開くとこのようになります:

image

レジストリのエクスポートは次のようになります:

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Connections]
"DefaultConnectionSettings"=hex:46,00,00,00,0a,00,00,00,01,00,00,00,00,00,00,\
  00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,\
  00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00
"SavedLegacySettings"=hex:46,00,00,00,51,00,00,00,0b,00,00,00,0c,00,00,00,31,\
  32,37,2e,30,2e,30,2e,31,3a,38,30,07,00,00,00,3c,6c,6f,63,61,6c,3e,00,00,00,\
  00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,\
  00,00,00,00,00,00,00,00
"Netkeeper"=hex:46,00,00,00,31,2c,30,30,0b,00,00,00,0c,00,00,00,31,32,37,2e,30,\
  2e,30,2e,31,3a,38,30,07,00,00,00,3c,6c,6f,63,61,6c,3e,00,00,00,00,00,00,00,\
  00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,\
  00,00,00,00
"greenvpn"=hex:46,00,00,00,02,00,00,00,01,00,00,00,00,00,00,00,00,00,00,00,00,\
  00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,\
  00,00,00,00,00,00,00,00,00,00

IP アドレスとポートの他に、いくつかの奇妙な 16 進数の文字列が見えます。参考記事 1 の紹介によると、大まかなパラメータは以下の通りです(画像は参考記事 1 から):

image

再度、SolomonXie大佬の参考記事 1 に感謝します:

まとめると、以下のようになります:

46 00 00 00 スイッチ 00 00 00 IP長 00 00 00 IPアドレス 00 00 00 ローカルプロキシをスキップするかどうか 21 00 00 00 PACアドレス

大佬の IE のバージョンはわかりませんが、私のは IE11 で、これとは少し異なります。おおよそ以下のようになります:

46 00 00 00 自増分 00 00 00 スイッチ 00 00 00 IP長 00 00 00 IPアドレス 00 00 00 ローカルプロキシをスキップするかどうか
  • 各情報の区切りは 3 つの 00、すなわち 00 00 00 です。

  • スイッチ:主に IE 設定でのチェックボックスの選択状態を示します。プロキシを使用する場合は 03、使用しない場合は 01、ローカルに対してプロキシを使用しないかどうかはこのスイッチに関係なく、最後のローカルプロキシをスキップする部分に依存します。設定を行った後、レジストリを確認することもできます。

  • 自増分:どの値から自増が始まるのかは不明ですが、設定が変更されない場合でも、再度「OK」をクリックするとこの自増分は開始されます。とりあえず直接 00 に設定しても問題ないことが後の効果で証明されました。

  • IP 長:16 進数で、. と:を含みます。例えば、127.0.0.1:80は 12 桁で、レジストリの値は0Cです。

  • IP アドレス:IP を各文字ごとに 16 進数に変換すれば良いです。

  • ローカルプロキシをスキップするかどうか:チェックされていない場合は全て 0、チェックされている場合は:

07 00 00 00 3c 6c 6f 63 61 6c 3e

ここで注意:もしここまで読んでいて、すでにコーディングを始めているなら、レジストリにインポートする 16 進数の値にはスペースやカンマがないことに注意してください。

  • この部分の 07 を除いて、残りの意味は:、これは固定値で変更する必要はありません。

  • 最後は全て 0 で埋めて、IP アドレスの項目の総長さを 224 に設定し、設定しない項目の総長さを 167 にします。

したがって、上記の内容に基づいて、私の状況に応じたローカルプロキシをスキップするレジストリ値は以下のようになります:

46 00 00 00 00 00 00 00 03 00 00 00 IP長 00 00 00 IPアドレス 00 00 00 07 00 00 00 3c 6c 6f 63 61 6c 3e

IP アドレスを 16 進数の文字列に変換し、長さを計算して渡すだけです。

レジストリ変更部分のコード:

レジストリにインポートする 16 進数の値にはスペースやカンマがないことに注意してください。

#!/usr/bin/python
# -*- coding: utf-8 -*-
'''
@author: soapffz
@function: レジストリを変更してシステムプロキシを設定する
@time: 19-04-26
'''
import IPy  # IPの合法性を判断
import re
import subprocess  # cmdコマンドを実行


def setproxy(hex_value):
    # 整理された16進数プロキシ設定を渡す
    try:
        vpn_name = "Netkeeper"  # あなたの専用ネットワークの名前
        subprocess.run('REG ADD "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Connections" /v "{}" /t REG_BINARY /d "{}" /f'.format(
            vpn_name, hex_value), shell=True, stdout=subprocess.PIPE)
        print("レジストリのインポート成功!")
    except Exception as e:
        print("レジストリの変更エラー:{}\nプログラム終了!".format(e))
        exit(0)


def registry_value_construction(ip, port, proxy_switch, local_proxy_switch):
    # レジストリ値の構築
    switch_options = {"1": "03", "0": "01"}  # プロキシスイッチオプション
    local_switch_options = {
        "1": "070000003c6c6f63616c3e", "0": ""}  # ローカルプロキシスイッチオプション
    # ポートの合法性チェックの正規表現
    port_regular_expression = r'^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$'
    if not re.search(port_regular_expression, port):  # ポートが0-65535の範囲にない場合、エラー
        print("ポートがタイプに合っていません\nプログラム終了!")
        exit(0)
    if not ip:  # IPが空の場合、ポートの設定を無視し、全てを0に設定
        former = "4600000000000000{}00000000000000{}".format(switch_options.get(
            proxy_switch), local_switch_options.get(local_proxy_switch))  # プロキシスイッチオプションとローカルプロキシスイッチオプションを埋め込む
        # 一部00を補完しないとインポートできない、以下同様
        value = former + "00"*int((112-int(len(former)))/2)
        print("IPが空のレジストリパラメータ値構築完了!")
    else:
        try:
            IPy.IP(ip)  # レジストリにIPとポートを自由に入力できますが、私たちは許可しません。IPが不正または空の場合、直接エラーが発生し終了します。
            # hex()メソッドで変換された数字は0xで始まり、IP長は2桁
            ip_len = hex(len(ip)+len(port)+1).split("x")[-1].zfill(2)
            ip = bytes(ip, 'ascii').hex()  # IPの16進数文字列を取得
            port = bytes(port, 'ascii').hex()
            former = "4600000000000000{}000000{}000000{}3A{}{}".format(switch_options.get(
                proxy_switch), ip_len, ip, port, local_switch_options.get(local_proxy_switch)).lower()
            value = former+"00"*int((150-int(len(former)))/2)
            print("レジストリパラメータ値構築完了!")
        except Exception as e:
            print("プログラムエラー:{}\nプログラム終了!".format(e))
            exit(0)
    return value


if __name__ == "__main__":
    ip = "127.0.0.1"  # プロキシIPを設定
    port = "80"  # プロキシポートを設定
    proxy_switch = "1"  # プロキシスイッチを設定、"1"はプロキシを有効に、"0"は無効に
    local_proxy_switch = "1"  # ローカルプロキシスイッチを設定、"1"は有効、ローカルにプロキシを適用しない、"0"は無効、ローカルにプロキシを適用
    hex_value = registry_value_construction(
        ip, port, proxy_switch, local_proxy_switch)  # この関数はプロキシ設定パラメータの16進数文字列を返します
    setproxy(hex_value)  # 16進数文字列を関数に渡して設定します

[19-05-03:ip_len 文を修正]

機能の説明:

  • IPy を使用した IP の合法性検査機能は、IP が空の場合にもエラーを報告するため、最初に IP が空かどうかを確認します。非空の場合はポート値を無視し、スイッチ以外は全て 0 に設定します。

  • コードで使用されているポートの合法性を検査する正規表現は0-65535を検査します。メモとして、1024-65535の正規表現は以下の通りです:

^(1(02[4-9]|0[3-9][0-9]|[1-9][0-9]{2})|[2-9][0-9]{3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$

効果のデモは以下の通り(コンピュータでクリックして大きな画像を表示):

image


[19-05-03 完結]

数日間深層学習のプロジェクトを書いていて、思考が少し混乱しているので、今日は少し休憩して前に書いた Windows ローカルプロキシクライアントの作成思路を整理します。

UI デザイン#

** 前提:** 前回、多スレッドで西刺プロキシのコードを書き換え、レジストリを通じてシステムプロキシを変更するこの 2 つの部分を完成させました。

コマンドラインを使うのはあまり楽しくないので、この機能を GUI で実現するのが適していると思い、いろいろ比較した結果、pyqt5を選びました(可視化デザインインターフェースが便利だからではありません)。

インストール#

私の環境:Win10+Anaconda3+pip/conda清華源

どうやらspyderというライブラリはpyqt5のサポートが必要で、直接pyqt5をインストールすると、更新されたバージョンが高すぎると表示され、spyderが不満を示しました。したがって:

pip install spyder
pip install pyqt5

基本ページの紹介#

pyqt5をインストールした後、Anaconda3をインストールする際に環境変数に追加することを選択しなかった場合は、自分のpythonパッケージのインストール場所に応じて環境変数を追加してください。

Anacondaがデフォルトで環境変数を追加した場合は、コマンドラインでdesignerと入力するだけで開きます。インターフェースと基本的な紹介は以下の通りです(コンピュータでクリックして大きな画像を表示):

image

image

UI は全て自分でデザインします。MainWindow上にいくつかの機能エリアをContainersウィジェットのFrameウィジェットで分けることをお勧めします。これにより、後で各機能エリアを処理するのが楽になります。

速成の参考:PyQT5 速成チュートリアル - 2 Qt Designer の紹介と入門

この翻滚吧挨踢男大佬の文章は非常にわかりやすいです:Python 初心者チュートリアル全目次

signal&slot#

PyQt5 には、イベントを処理するための独自の signal&slot(シグナルスロット)メカニズムがあります。シグナルスロットはオブジェクト間の通信に使用されます。シグナルは特定のイベントが発生したときにトリガーされ、スロットは任意の呼び出し可能なオブジェクトです。シグナルがトリガーされると、接続されたスロットが呼び出されます。

つまり、誰が送信者で誰が受信者かを設計する必要があります。ここで例を挙げます:

** designerで F3 と F4 を押して UI デザインインターフェースとシグナルとスロット編集インターフェースを切り替えます **

シグナル送信者をシグナル受信者にドラッグすると、シグナルとスロットの編集インターフェースが表示されます。ここでは、comboboxtextbrowserにドラッグします:

image

** soapffzの提案:初期段階では、各異なるウィジェットの一般的なメソッドをすべてクリックして、エクスポートされたPythonコードを確認し、文を構築し、後期には UI を設計し、他のすべてのシグナルとスロットをロジック部分のコードで自分で記述し、UI とロジックを分離します。**

参考記事:やはり翻滚吧挨踢男大佬の文章:PyQt5 学習ノート 05----Qt Designer シグナルスロット

コードのエクスポート#

基本的な UI デザインが完了したら、プロキシクローリングのドロップダウンリストにシグナルスロットを追加して、エクスポートされたコードの構成を説明します。以下のように示します:

image

右下のインターフェースでも確認できます:

image

私の環境でpyqt5をインストールした後、cmdpyuic5 -o xx.py xx.uiコマンドを実行できます。

もしpyuic5が実行できない場合は、自分で検索してください。デモは以下の通りです:

image

エクスポートされたコードの構成は以下の通りです:

# -*- coding: utf-8 -*-

# UIファイル'local_prixies.ui'から読み取って生成されたフォーム実装
#
# 作成者:PyQt5 UIコードジェネレーター 5.11.3
#
# 警告!このファイルに加えたすべての変更は失われます!

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(640, 480)
        MainWindow.setMinimumSize(QtCore.QSize(640, 480))
        MainWindow.setMaximumSize(QtCore.QSize(640, 480))
        各種ウィジェットのサイズ、位置、幅、高さなどの属性
        self.retranslateUi(MainWindow)
        追加したシグナルとスロットはここに
            self.comboBox_proxychoose_crawl.currentTextChanged['QString'].connect(self.textBrowser_disp.append)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "winローカルプロキシ設定 - by soapffz"))
        self.label_funcchoose.setText(_translate("MainWindow", "機能選択"))
        各種ウィジェット内の内容

エクスポートされたコードには 1 つのクラスしかなく、インスタンス化する必要があります。この部分で重要な内容を引き入れます:UI とコードロジックの分離

UI とコードロジックの分離#

1. 分離されていない状態

エクスポートされた py ファイルに以下のコードを追加します(クラス名が UI_MainWindow であることを確認してください):

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    MyWindow = Ui_MainWindow()
    MyWindow.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

これでエクスポートされたpyファイルでそのインターフェースを起動できます(大きな GIF):

image

2. 分離された状態

新しいmainwindow.pyファイルを作成し、主にインターフェースのインスタンス化とすべてのロジック実装を行い、UI とロジックの分離を実現します。この中に以下のコードを追加します(エクスポートされたpyファイルの名前がui.pyであると仮定します):

from PyQt5 import QtCore, QtGui, QtWidgets
from ui import Ui_MainWindow


class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        # UIインターフェースをインスタンス化
        self.setupUi(self)
        # インスタンス化後、selfはUI_Mainwindowのすべての属性を持ちます
        # この部分でシグナルスロットや残りのUI部分を編集します。UIとコードロジックを分離します。

新しいmain.pyファイルを作成し、主に起動用に使用します。この中に以下のコードを追加します:

from PyQt5 import QtCore, QtGui, QtWidgets
from mainwindow import MainWindow
from sys import argv
from sys import exit as sys_exit

if __name__ == "__main__":
    app = QtWidgets.QApplication(argv)  # コマンドライン引数を取得
    mainWindow = MainWindow()  # インターフェースインスタンスを作成
    mainWindow.show()  # インターフェースを表示
    sys_exit(app.exec_())

その後、main.pyを実行するとインターフェースが表示されます。小さな提案:

  • 慣れたら、designerで UI だけを作成し、シグナルとスロットはすべてmainwindow.pyのコメント部分に追加します。
  • インスタンス化時に必要なパラメータもmainwindow.pyに置き、ここでの例を挙げます:

プロキシクローリング部分のcombobox西刺高匿他は未定の 2 つのドロップダウンオプションを追加する必要があります。UI デザイン時にこれを記述するのではなく、mainwindow.pyで初期化時にプロキシ名リストを追加することで実現できます。コードは以下の通りです:

mainwindow.py

from PyQt5 import QtCore, QtGui, QtWidgets
from ui import Ui_MainWindow


class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    # UIとロジックを分離するために、基本的なロジック操作はこのクラスで完了します。
    def __init__(self):
        # 初期化部分はこの関数に直接置きます。
        super().__init__()
        # UIインターフェースをインスタンス化
        self.setupUi(self)
        # プロキシの名前、もし新しいプロキシ関数があればここに名前を追加します。
        self.proxyname_l = ["西刺高匿", "他は未定"]
        self.ui_remaining_part()

    def ui_remaining_part(self):
        # 基本のUIインターフェースに加え、この関数で各ウィジェットの初期値を設定し、各オブジェクトをインスタンス化します。
        # プロキシの名前リストを2つのプロキシのcomboboxに挿入します。
        self.comboBox_proxychoose_crawl.addItems(self.proxyname_l)
        self.comboBox_proxychoose_setup.addItems(self.proxyname_l)

したがって、UI を初期化する際に必要な他のパラメータや、他の機能型クラスのインスタンス化の文をmainwindow.pyのインスタンス化後の部分に記述します。

これにより、重要なウィジェットのエイリアスを変更するだけで、UI を設計する際に残りの属性やシグナルとスロットをmainwindow.pyで楽しく編集でき、UI インターフェースが少し変更されても元のコードロジックに影響を与えず、UI とコードロジックの分離を実現します。

参考記事:PyQt5 で UI とロジックを分離する方法の概要

シグナルとスロットで追加パラメータを渡す#

シグナルとスロットで追加パラメータを渡すことの重要性は、上記で述べた UI とコードロジックの分離に劣らないものです。

上記で既に述べたように、UI を設計する際には設計だけを行い、残りのシグナルとスロットはすべてロジック部分で実装します。これにより、各ウィジェットがトリガーできるイベントやどのようなスロットがあるかを熟知する必要があります。ウィジェットがトリガーするイベントがシグナルとスロットに渡す形式は基本的に以下のようになります:

self.ウィジェット.イベント.connect(スロット)

例えば、上記の GIF デモでプロキシクローリングのドロップダウンリストの内容を変更すると、表示エリアに出力するための文は以下のようになります:

self.comboBox_proxychoose_crawl.currentTextChanged['QString'].connect(self.textBrowser_disp.append)

.connectの中のスロットは、前のイベントから発信されたシグナルを受け取ります。受け取るシグナルは何でしょうか?いくつかの一般的な例を挙げます:

  • ボタンがクリックされた場合、スロットが受け取るのは `` です。
  • ドロップダウンリストが変更された場合、スロットが受け取るのはドロップダウンリストの変更後の値です。
  • チェックボックスの状態が変更された場合、スロットが受け取るのはチェックボックスの状態が変更されたときの状態の信号数値で、2 は選択、0 は未選択、1 は半選択です。

これらは私がコードを書く中で調べた資料から得たものですが、もし初めて使うウィジェットを使用する場合、特定のウィジェットの特定のイベントがトリガーされたときに渡されるシグナルが何かを迅速に知りたい場合は、次のようにします:

class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    # UIとロジックを分離するために、基本的なロジック操作はこのクラスで完了します。
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.ウィジェット.イベント.connect(self.カスタム関数)

    def カスタム関数(self, parameter):
        print(parameter)

これにより、選択したウィジェットの対応するイベントをトリガーするたびに、渡されたパラメータが印刷されます。

つまり、各ウィジェットが特定のイベントをトリガーする際に、自身の特定のパラメータを持っていることがわかりますが、実験の結果、後の connect は括弧を持たない関数でなければなりません。

したがって、もし私たちがこの渡されたパラメータをカスタマイズしたい場合は、lambdaを使用することで追加のパラメータを渡すことができます。

例えば、今、2 つのボタンbt1bt2があり、そのうちの 1 つをクリックしたときに、どのボタンをクリックしたかを知りたい場合、コードは以下のようになります:

        self.pushbutton_1.clicked.connect(lambda:self.whichbt(1))
        self.pushbutton_2.clicked.connect(lambda:self.whichbt(2))
    def whichbt(self, i):
        print("今、{}号ボタンを印刷しました".format(i))

このように、.connectの後の関数は括弧を持ち、括弧の中に複数のパラメータを持つことができます。また、自身を渡すこともできます:

        self.checkBox_1.stateChanged.connect(lambda: self.which_checkbox(self.checkBox_1, 1))
        self.checkBox_2.stateChanged.connect(lambda: self.which_checkbox(self.checkBox_2, 2))

    def which_checkbox(self, part, i):
        part.setChecked(False)
        print("checkbox{}の状態が変更されましたが、今は未選択に設定しました".format(i))

しかし、元のパラメータを渡し、追加のパラメータも渡したい場合、どうすればよいのかわからないので、知っている方はコメントで教えてください。

参考記事:pyqt シグナルとスロットで追加パラメータを渡す

重要な部分の紹介が完了したので、次に私がコードを書く中で遭遇した小さな問題とその解決策をいくつか紹介します。

行入力#

ランダムプロキシを設定するだけでなく、カスタムプロキシも追加しましたので、ユーザーがカスタム入力するipportを検証する必要があります。調査中に優れた記事を見つけました:PyQt5 基本ウィジェットの詳細と QLineEdit(四)

ここから、テキストボックスの入力制限の設定方法を学び、最終的にipportの部分のコードは以下のようになります:

# ipはデフォルトで書き込み不可、IPアドレスマスク;ただし、ここでは入力タイプを数字に制限するだけで、IPの合法性を検証する必要があります。
self.lineEdit_customizeip.setInputMask('000.000.000.000;_')
self.lineEdit_customizeip.setReadOnly(True)
# ポートはデフォルトで書き込み不可で、ポートは0-65535に制限されます。
# テキストに許可される内容を設定
port_reg = QtCore.QRegExp(r"^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$")
# カスタムテキストバリデータ
pportregValidator = QtGui.QRegExpValidator(self)
# 属性を設定
pportregValidator.setRegExp(port_reg)
# バリデータを設定
self.lineEdit_customizeport.setValidator(pportregValidator)
self.lineEdit_customizeport.setReadOnly(True)

ipを設定すると、開くとマスクの入力ボックスが表示されます:

image

私が制限した部分以外は入力できず、テキストボックスに表示されませんので、実際の効果は表示しません。

2 つのチェックボックスの相互排他#

私はCheck Boxを使用して、プロキシ方式がランダムプロキシかカスタムプロキシかを選択していますが、デフォルトでは相互排他ではありません。

参考記事:PyQt5 シリーズチュートリアル(15):単一選択ボタン

単一選択ボタンはデフォルトで autoExclusive(自動排他)です。この自動排他機能が有効になっている場合、同じ親ウィジェットに属する単一選択ボタンの動作は同じ排他ボタングループの一部となります。もちろん、QButtonGroup に追加することで複数の単一選択ボタンの相互排他を実現できます。

したがって、ランダムプロキシとカスタムプロキシの 2 つのボタンを 1 つの相互排他の QButtonGroup に追加して、単一選択効果を実現します。コードは以下の通りです:

# 2つのチェックボックスを相互排他のQButtonGroupに入れて、単一選択効果を実現します。
self.btgp_mutuallyexclusive = QtWidgets.QButtonGroup(self.groupBox_setting)
self.btgp_mutuallyexclusive.addButton(self.checkBox_randomproxy)
self.btgp_mutuallyexclusive.addButton(self.checkBox_customizeproxy)

QButtonGroup に追加する前の効果を見てみましょう:

image

追加後の効果を見てみましょう:

image

ウィンドウを閉じるときにイベントをバインドする#

ウィンドウを閉じるときに、プロキシクリア関数をトリガーしたいと思っています。これにより、プログラムを終了しても通常の使用に影響を与えません。参考記事を見つけました:

PyQt5 プログラミング(17):ウィンドウイベント

この記事は少し乱雑ですが、実装の効果は、閉じるボタンをクリックすると、閉じるかどうか確認する標準ダイアログが表示されるというものです。

ウィンドウのタイトルバーの閉じるボタンをクリックするか、close () メソッドを呼び出してウィンドウを閉じると、closeEvent (self, event) メソッドが呼び出されます。event パラメータを通じて、QCloseEvent クラスのオブジェクトを取得できます。ウィンドウを閉じるのを防ぐには、このオブジェクトを通じて ignore () メソッドを呼び出す必要があります。そうでなければ、accept () メソッドを呼び出します。以下の例は、閉じるボタンをクリックすると、ウィンドウを閉じるかどうか確認するダイアログが表示されます。ユーザーが「はい」ボタンをクリックするとウィンドウが閉じられ、「いいえ」ボタンをクリックするとダイアログだけが閉じられ、ウィンドウは閉じられません。

コードは以下の通りです:

import sys
from PyQt5 import QtWidgets


class MyWindow(QtWidgets.QWidget):
    def init(self):
        QtWidgets.QWidget.init(self)
        self.resize(300, 100)

    def closeEvent(self, e):
        result = QtWidgets.QMessageBox.question(
            self, "ウィンドウ閉鎖確認", "本当にウィンドウを閉じますか?", QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)
        if result == QtWidgets.QMessageBox.Yes:
            e.accept()
            QtWidgets.QWidget.closeEvent(self, e)
        else:
            e.ignore()


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    window = MyWindow()
    window.show()
    sys.exit(app.exec_())

実装の効果は以下の通りです:

image

したがって、クリックして閉じるときにプロキシをクリアする関数をバインドできます:

def closeEvent(self, e):
    # 閉じるボタンをクリックしたときに設定をクリアします
    QtWidgets.QWidget.closeEvent(self, self.pbp_of_clearsetup())

機能的コードの最適化#

機能的なコードはすでに完成していますが、コードロジックを書く中で、元のコードの不完全な部分やいくつかの機能がまだ実装されていないことに気づきましたので、ここで補足説明します。

プロキシのクローリングとデータベース操作#

プロキシをクローリングする際、クローリングとデータベースに保存する部分だけを書きましたが、必要な機能は以下の通りです:

プロキシを開始する際には、データベースからipportを読み取り、対応するプロキシ専用のリストに保存します。別のプロキシに切り替えるときは、現在のプロキシを破棄し、リストからランダムに別のプロキシを選択します。プロキシセットを切り替えたときは、保存リストをクリアし、次回プロキシを開始するときに再度データベースから読み取って対応するリストに保存します。これにより、新たにクローリングしたプロキシが使用されることが保証されます。

私はプロキシをクローリングする機能とデータベースからプロキシを読み取る機能を 1 つの関数にまとめ、データベース接続などのコードの重複を避けました。部分コードは以下の通りです:

try:
    # 対応するコレクションからipとport情報を読み取ります
    collection_dict_l = self.db["{}".format(collection_name)].find({}, {
        "_id": 0, "ip": 1, "port": 1})
    for item in collection_dict_l:
        # 重複排除操作
        if item not in proxies_l:
            # 対応する集合リストに追加します。リストの要素形式はipとportの辞書です。
            proxies_l.append(item)
    return proxies_l
except Exception as e:
    return("プログラムエラー:{}".format(e))

参考記事:Python Mongodb で文書をクエリする

他の参考記事:

プロキシ設定部分#

これは本当に自分を困らせました。

元のコードのレジストリ構築関数では、IP が空でポートが不正であることをフィルタリングした後、パラメータ値を構築するコードは以下のようになります:

try:
    IPy.IP(self.ip)  # レジストリにIPとポートを自由に入力できますが、私たちは許可しません。IPが不正または空の場合、直接エラーが発生し終了します。
    ip_len = hex(len(self.ip)+len(self.port)+1).replace('x', '')
    ip = bytes(self.ip, 'ascii').hex()  # IPの16進数文字列を取得
    port = bytes(self.port, 'ascii').hex()
    former = "4600000000000000{}000000{}000000{}3A{}{}".format(switch_options.get(
        self.proxy_switch), ip_len, ip, port, local_switch_options.get(self.local_proxy_switch)).lower()
    hex_value = former+"00"*int((150-int(len(former)))/2)
    print("レジストリパラメータ値構築完了!")
except Exception as e:
    print("プログラムエラー:{}\nプログラム終了!".format(e))
    exit(0)

その中で、ip:とポートの長さが 15 桁を超える場合の構築の行が以下のようになります:

  • 127.0.0.1:80:12 桁、ip_len=0C
  • 110.110.110.110:65535:21 桁、ip_len=015

パラメータ値は以下のように変化します:

プロキシスイッチ0000000C
プロキシスイッチ000000015

中央に 0 が 1 つ多くなります!これにより、パラメータが4600...から04600...に無駄に変わり、プロキシが機能しなくなります。

したがって、ip_len文を以下のように修正しました:

# hex()メソッドで変換された数字は0xで始まり、IP長は2桁
ip_len = hex(len(ip)+len(port)+1).split("x")[-1].zfill(2)

これで問題が解決しました。この話は、細心の注意を払うことが重要であることを教えてくれます。

完結#

最終的な効果は以下の通りです。

1.. プロキシクローリング部分:

データベースが空で、最近 IP が封鎖された状態(コンピュータでクリックして大きな画像を表示):

image

注:これは交互に一時停止して録画した効果で、GIF のサイズを小さくするために大部分のフレームを抽出しました。実際のイベントはソフトウェアの表示エリアのタイムスタンプを参照してください。

2. プロキシ部分(コンピュータでクリックして大きな画像を表示):

image

  • 自分を守るために、元の IP はマスクされています。
  • コードは現在完全に完成していないため、接続速度が良くない IP に遭遇する可能性があります。この GIF は接続速度が良好なプロキシに遭遇した場合の理想的な状態を示しています。githubプロジェクトを公開する際には、接続前に検証する機能を追加します。
  • 注:これは交互に一時停止して録画した効果で、GIF のサイズを小さくするために大部分のフレームを抽出しました。実際のイベントはソフトウェアの表示エリアのタイムスタンプを参照してください。

全コードが多すぎるので、ここでは表示しません。深層学習のプロジェクトが完了したら、このプロジェクトをgithubにオープンソースとして公開し、最初のオープンソースプロジェクトとして使用します。

皆さんも私にstarを付けたり、issuesを提起したりしても構いません。

最後に、自分が構想していた UI デザイン図を 1 枚貼ります:

image

現在のソフトウェアインターフェース:

image

この記事は完結です。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。