Python 3のソケットを介したオンデマンドのシステムコマンドのリモート実行、またはサイトのダウンロード方法

このプロジェクトは、実用的な目的よりも教育目的(Pythonでのネットワークプログラミングの学習)のために作成されました。 いくつかの記事を読むためにサイトをダウンロードする人はほとんどいないので、同じ役割も担います(これが本当に役立つ場合を除きます)。



少し前まで、私の都市のモバイルインターネットの品質は、オペレーターのネットワークへの負荷の増加により徐々に低下し始め、多数の接続(依存するページファイル)を必要とする一部のサイトは非常にゆっくりとロードを開始しました。 夕方には、速度が大幅に低下するため、一部のサイトは数十秒以内に完全にロードできます。



この問題を解決するにはいくつかの方法がありますが、私は少し変わった方法を選択することにしました。 サイトをダウンロードすることにしました。 もちろん、この方法はHabrなどの大規模なサイトには適していません。パーサーを使用する方が合理的ですが、別のハブ、ユーザーのリスト、またはフィルターを適用してHTTrack Website Copierを使用する出版物のみをダウンロードできます。 たとえば、HabrからPythonハブをダウンロードするには、フィルター「+ habrahabr.ru / hub / python / *」を適用する必要があります。



このメソッドは、さらにいくつかの目的に使用できます。 たとえば、飛行機などでインターネットに接続していない状態になる前に、サイトまたはその一部をダウンロードする場合。 または、ロシア連邦の領土でブロックされたサイトをダウンロードするために、非常に遅いTorを介して、またはサイトデータが禁止されていない別の国のコンピューターを介してそれらをダウンロードし、ロシア連邦にあるコンピューターに転送すると、複数ページのサイトの場合は高速です。 したがって、たとえばドイツまたはオランダのサーバーを介してxHamster Wikipediaをダウンロードし、SFTP、FTP、HTTPまたはその他の便利なプロトコルを使用してサイトを圧縮形式で取得できます。 もちろん、そのような大きなサイトに十分なスペースがある場合:)



さあ、始めましょう!? アプリケーションは徐々に複雑になり、新しい機能が追加されます。これにより、ここで何が起こっているのか、それがどのように機能するのかを理解できます。 Pythonを知らない人でも理解できるように、コードには十分な数のコメントを添付しますが、コードが乱雑にならないように、既に説明したコードや関数についてはコメントしません。 サーバーとクライアントの両方がLinuxで作成およびチェックされますが、 理論的には、すべての必要なアプリケーション、つまりhttracktarがインストールされていれば、 他のプラットフォームで機能するはずです。また、必要なパスは、構成ファイルで設定されます。 プラットフォームでの起動に問題がある場合は、コメントを記入してください



最初に、文字列をクライアントに転送する単純なサーバーを実装します。



# FILE: server.py import socket #  IPv4    (TCP/IPv4) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #     localhost   65042 sock.bind(("localhost", 65042)) #   sock.listen(True) #    while True: #                conn, addr = sock.accept() #     print('Connected by', addr) #    ,    1024  data = conn.recv(1024) #        conn.sendall(data)
      
      





さらに単純なクライアントを実装し、受信した(つまり、同じサーバーに送信した)文字列を出力します。



 # FILE: client.py import socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #    sock.connect(("localhost", 65042)) sock.sendall(b"Hello, world") #    ,    1024  data = sock.recv(1024) #   sock.close() #    print(data.decode("utf-8"))
      
      





出力では、 デコード(元の)メソッドを使用して、バイト配列から文字列を取得しました。 バイト配列を復号化するには、エンコードを指定する必要があります。この場合はUTF-8です。



ここでしばらく停止して、アプリケーションの使用方法、使用されるコマンド、およびクライアントとサーバー間の通信がどのようになるかを考える必要があります。



アプリケーションをときどき使用する予定なので、利便性を考慮してあまり入浴する必要はありません。 アプリケーションを実行できるのは何ですか? まず、サイトをダウンロードすることです。 サーバーアプリケーションがサイトをダウンロードしました。 本当に会いたいですよね? これを行うには、サーバーマシンからクライアントマシンにファイルを転送する必要があります。ファイルの数は非常に多く、接続を確立するときに大きな問題が発生するため、すべてを圧縮しておくと便利でした。 ダウンロードしたサイトを表示できるようになればいいのですが、それについては後で詳しく説明します。



サーバーに送信されるコマンドの形式は次のとおりです。

 <command> [args]
      
      





例:

 dl site.ru 0 gz list list during
      
      





まず、クライアントを少し変更します。 交換

  sock.sendall(b"Hello, world")
      
      







  sock.sendall(bytes(input(), encoding="utf-8"))
      
      





これで、キーボードから入力された任意のコマンドをサーバーに送信できます。



サーバーに移りましょう。ここではすべてがより複雑です。



まず、 httrack.pyconfig.pyの 2つのファイルを作成します。 1つ目はHTTrackを管理するための機能を含み、2つ目はクライアントとサーバーの構成を含みます(共有されます)。 必要に応じて、サーバーとクライアントの構成ファイルを個別に作成し、Python形式ではなく、構成.iniなどを使用できます。



2番目のファイルでは、すべてがシンプルで明確です。

 from os import path host = 'localhost' port = 65042 #    .    -  <b>Sites</b>   .  ,      ,      . sites_directory = path.expanduser("~") + "/Sites"
      
      





最初のファイルに移る前に、標準サブプロセスライブラリの呼び出し関数について少し説明します。

 subprocess.call(args)
      
      





この関数は、 args配列で渡されたコマンドを実行します。 この関数は、 args配列からコマンドを実行するディレクトリを指定するcwdパラメーターを取ることもできます。 実行可能なコマンド(プログラムと呼ばれる)の完了を待機し、完了コードを返します。



ここで、HTTrackを管理する唯一の機能を作成します。これにより、必要なディレクトリにサイトをダウンロードできます。

 # FILE: httrack.py from subprocess import call from os import makedirs #        import config def download(url): #     ( , ),   . if url.find("//"): url = url[url.find("//")+2:] #     if url[-1:] == '/': url = url[:-1] site = config.sites_directory + '/' + url print("Downloading ", url, " started.") #  ,       makedirs(config.sites_directory, mode=0o755, exist_ok=True) #  HTTrack     call(["httrack", url], cwd=config.sites_directory) print("Downloading is complete")
      
      





server.pyを変更します。



 import socket import threading #      import httrack import config def handle_commands(connection, command, params): if command == "dl": #    (, ,  )  HTTrack' htt_thread = threading.Thread(target=httrack.download, args=(params[0])) #    htt_thread.start() connection.sendall(b'Downloading has started') else: connection.sendall(b"Invalid request") def args_analysis(connection, args): #       .  "dl site.ru 0 gz"   ["dl", "site.ru", "0", "gz"]. args = args.decode("utf-8").split() # [1:] - .   ,     . handle_commands(connection=connection, command=args[0], params=args[1:]) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind((config.host, config.port)) sock.listen(True) while True: conn, addr = sock.accept() print('Connected by ', addr) data = conn.recv(1024) args_analysis(connection=conn, args=data)
      
      





ここでは、すべてが明確だと思います。 コードは、組み合わせてコードを簡素化できる機能を備えているため、少し複雑ですが、その後のコードの変更に役立ちます。



現時点では、最初にserver.pyを起動し、次にclient.pyを起動できます。 クライアントアプリケーションで、次のコマンドを入力します。

 dl http://verysimplesites.co.uk/
      
      





約1分後、インターネット接続に応じて、サーバーアプリケーションに「 ダウンロードが完了しました 」と表示され、ホームフォルダーにサイトフォルダーが表示されます。このフォルダーには、開くことができるダウンロード済みサイトが既に含まれているverysimplesites.co.ukディレクトリーが含まれていますインターネットに接続していないブラウザで。



しかし、これは私たちにとって十分ではありません。なぜなら、サイトを圧縮された形式で、アーカイブで利用できるようにしたいからです。 ここで、 dlコマンドに1つではなく3つの引数を設定します。 最初は同じままで、これはダウンロードする必要があるサイトです。 2番目は、ダウンロードの完了後にディレクトリを削除するかどうかを示すフラグです。 3番目は、ダウンロード後(必要な場合は削除前)にサイトがパッケージ化されるアーカイブ形式です。



server.pyの httrackプロセスのステータスをチェックする機能:

 def dl_status_checker(thread, connection): if thread.isAlive: connection.sendall(b'Downloading has started') else: connection.sendall(b'Downloading has FAILED')
      
      





server.pyの dlコマンド:

 if command == "dl": #    ,     <b>"0"</b> if params[1] == '0': params[1] = False else: params[1] = True #            ,      if not params[1] and len(params) == 2: params.append(None) htt_thread = threading.Thread(target=httrack.download, args=(params[0], params[1], params[2])) htt_thread.start() #  2  ,     HTTrack dl_status = threading.Timer(2.0, dl_status_checker, args=(htt_thread, connection)) dl_status.start()
      
      





httrack.py

 from subprocess import call from os import makedirs from shutil import rmtree import config def download(url, remove, archive_format): if url.find("//"): url = url[url.find("//")+2:] if url[-1:] == '/': url = url[:-1] site = config.sites_directory + '/' + url print("Downloading ", url, " started.") makedirs(config.sites_directory, mode=0o755, exist_ok=True) call(["httrack", url], cwd=config.sites_directory) print("Downloading is complete") if archive_format: if archive_format == "gz": # : <b>tar -czf /home/user/Sites/site.ru.tar.gz -C /home/user/Sites /home/user/Sites/site.ru</b> call(["tar", "-czf", config.sites_directory + '/' + url + ".tar.gz", "-C", config.sites_directory, url], cwd=config.sites_directory) elif archive_format == "bz2": call(["tar", "-cjf", config.sites_directory + '/' + url + ".tar.bz2", "-C", config.sites_directory, url], cwd=config.sites_directory) elif archive_format == "tar": call(["tar", "-cf", config.sites_directory + '/' + url + ".tar", "-C", config.sites_directory, url], cwd=config.sites_directory) else: print("Archive format is wrong") else: print("The site is not packed") if remove: rmtree(site) print("Removing is complete") else: print("Removing is canceled")
      
      





多くの新しいコードが登場しましたが、その中に複雑なものはなく、ほんのいくつかの新しい条件が登場しました。 新しい関数のうち、 rmtreeのみが表示され、最後にあったすべてのものを含め、渡されたディレクトリが削除されます。



パラメータなしの単純なリストコマンドをhandle_commands関数に追加できます。

 elif command == "list": #          file_list = listdir(config.sites_directory) folder_list = [] archive_list = [] #   ,       ,   for file in file_list: if path.isdir(config.sites_directory + '/' + file) and file != "hts-cache": folder_list.append(file) if path.isfile(config.sites_directory + '/' + file) and \ (file[-7:] == ".tar.gz" or file[-8:] == ".tar.bz2" or file[-5:] == ".tar"): archive_list.append(file) site_string = "" folder_found = False #     if folder_list: site_string += "List of folders:\n" + "\n".join(folder_list) folder_found = True if archive_list: if folder_found: site_string += "\n================================================================================\n" site_string += "List of archives:\n" + "\n".join(archive_list) if site_string == "": site_string = "Sites not found!" connection.sendall(bytes(site_string, encoding="utf-8"))
      
      





最初に必要なライブラリを接続しました:

 from os import listdir, path
      
      





client.pyのサーバーからクライアントが受信するデータの最大サイズを増やすと便利です。

  data = sock.recv(65536)
      
      





server.pyを再起動してclient.pyを実行します 。 始めるために、サーバーに注文してサイトをダウンロードし、tar.gzアーカイブにパックしてから削除します。

 dl http://verysimplesites.co.uk/ 1 gz
      
      





その後、別のサイトをダウンロードしますが、パックしません。もちろん、削除します:

 dl http://example.com/ 0
      
      





約1分後に、サイトのリストを確認します。

 list
      
      





同じコマンドを入力した場合、サーバーから次の応答を受け取るはずです。

 List of folders: example.com ================================================================================ List of archives: verysimplesites.co.uk.tar.gz
      
      







今日はすみません、それだけです。 もちろん、これは実装可能なすべての実装からは程遠いものですが、それにもかかわらず、そのようなアプリケーションの動作の一般的な原理を理解することができます。 このトピックに関心がある場合は、コメントがある場合はコメントに書いてから、時間をかけて、このアプリケーションのもう少し機能について記事を書きます。サイトのダウンロードステータスの表示を含め、侵入から保護するためのいくつかの方法を示します。シェルへの侵入に対する保護を含む、見知らぬ人のサーバーへ。



UPD:パート2。 データ転送プロトコル



All Articles