Wir umgehen das Verbot von Nachrichten API Vkontakte über Python

Hallo Habr. In meinem vorherigen Artikel habe ich über die Möglichkeit gesprochen, über die Dokumentation auf die Methoden des Nachrichtenabschnitts zuzugreifen, für die es ausreichte, sich bei der VK-Site anzumelden. Viele sagten dann, dass dies keine Bedrohung für die persönlichen Daten der Benutzer darstellt und dass die Unfähigkeit, ihre Nachrichten herauszupumpen, das Fehlen einer Plattform ist. Auch in den Kommentaren wurde mir ein Link zur Bibliothek node.js hinterlassen , die sich mit Login / Passwort anmelden und Zugriff auf die Nachrichten-API gewähren kann, wobei sie vorgibt, eine offizielle Anwendung zu sein.

Haftungsausschluss:


Der Artikel und der gesamte geschriebene Code wurden nur zu Bildungs- und Forschungszwecken erstellt und nie für illegale Aktivitäten verwendet. Der Autor fordert Sie nicht auf, eine der hier beschriebenen Aktionen zu wiederholen, und trägt keine Verantwortung dafür.


Da jedoch nicht alle Benutzer mit Javascript und node.js vertraut sind, habe ich beschlossen, meine Bibliothek in Python zu schreiben, das jetzt von vielen Benutzern verwendet wird. Dadurch können wir die volle Funktionalität der Nachrichten-API über die "Testanforderungen" der Dokumentation bereitstellen. Ich bitte Sie sofort, nicht böse auf mich zu sein, wenn ich Aspekte der vergangenen „Rede“ wiederholen werde, weil ich diesen Artikel in Form einer unabhängigen Dokumentation anordnen möchte.

Wie benutzt man es?


Die Bibliothek selbst befindet sich im github-a-Repository (an derselben Stelle, im Beispielordner, befindet sich ein Skript mit Anwendungsbeispielen aus diesem Artikel). Um es auf einem Computer zu installieren, können Sie den folgenden Befehl im Terminal verwenden:
pip install vk-messages

Jetzt können wir die Hauptklasse aus diesem Paket importieren und eine Instanz davon erstellen, wobei wir die Anmeldung, das Kennwort, die Art der von diesem Konto verwendeten Autorisierung sowie das Verzeichnis angeben, in dem Benutzerautorisierungscookies gespeichert werden sollen. Dies ist erforderlich, damit Benutzer mit Zwei-Faktor-Berechtigung nicht bei jeder Ausführung des Skripts ständig den Code aus der Nachricht eingeben müssen.

from vk_messages import MessagesAPI

login, password = 'login', 'password'                                
messages = MessagesAPI(login=login, password=password,
                                two_factor=True, cookies_save_path='sessions/')

Und tatsächlich ist das alles. Jetzt müssen wir nur noch die Dokumentation öffnen und die Methoden verwenden, an denen wir interessiert sind. Ich möchte sofort darauf hinweisen, dass dieser Ansatz es uns ermöglicht, fast jede Methode aus der Dokumentation zu verwenden, auch wenn sie nicht mit dem Nachrichtenabschnitt zusammenhängt:

history = messages.method('messages.getHistory', user_id='1234567', count=5)

Wir können diese Bibliothek auch mit anderen kombinieren, z. B. über vk_api können wir Fotos von einem Computer hochladen (der Code für diese Aktion finden Sie im Beispielabschnitt), und über vk_messages können Sie diese Anhänge an die Nachricht anhängen:

from vk_messages.utils import get_random

messages.method('messages.send', user_id=peer_id, message='Hello',
            attachment='photo123456_7891011', random_id=get_random())

Aus Neugier habe ich die klassische Funktion implementiert, die in einem bestimmten Ordner Unterordner von Personen erstellt, mit denen eine Person gesprochen hat, und versucht, die neuesten Nachrichten und absoluten URLs von Fotos herauszupumpen. Zum Glück funktionierte alles wie eine Uhr und es gab keine unnötigen Fehler:

from vk_messages.utils import fast_parser

fast_parser(messages, path='parsing/',                    
       count_conv=10, messages_deep=400, photos_deep=100) 

Jetzt möchte ich zu einem der interessantesten Teile dieser Bibliothek übergehen: Mit Autorisierungscookies können wir absolut jede Aktion ausführen. Lassen Sie mich ein persönliches Beispiel geben: Wenn ich für die Beiträge der Gruppe, in der ich Mitglied bin, eine Tabelle erstellen musste, die aus der Beitrags-ID und ihrem Autor besteht . Aber was war der Haken: Die offizielle API gibt nur die Person zurück, die den Artikel veröffentlicht hat. Mit dem Sniffer habe ich gesehen, dass diese Daten vom Server heruntergeladen werden, wenn Sie am Datum nach der Veröffentlichung schweben. Danach habe ich einen Wrapper geschrieben, mit dem Sie so viele ähnliche Anfragen senden können, wie Sie möchten. Dabei haben Sie nur den Post-Link und die Autorisierungs-Cookies verwendet, um die Autoren zu ermitteln. Im folgenden Beispiel müssen nur unnötige Tags entfernt werden:

def get_creators(post, cookies):
    group = -int(post.split('_')[0])
    response = requests.post('https://vk.com/al_page.php', cookies=cookies,
               data=f"_ads_group_id={group}&act=post_author_data_tt&al=1&raw={post}")

    response_json = json.loads(response.text[4:])['payload'][1]
    return response_json[0]

authors = get_creators(post='-12345_67890',
                    cookies=messages.get_cookies())
print(authors)   

Aber was beweist uns der oberste Code? Selbst wenn VC Testanforderungen in seiner Dokumentation abschließt, können wir immer Benutzeraktionen simulieren und die erforderlichen Informationen abrufen. Als Experiment habe ich eine kleine Funktion erstellt, die durch Anfragen zum „Scrollen“ der Seite Links zu Fotos erhalten kann, ohne die offizielle API zu verwenden.

Der Code war zu groß, deshalb habe ich beschlossen, ihn auszublenden
def get_attachments(attachment_type, peer_id, count, offset, cookies_final):
    if attachment_type == 'photo':
        session = requests.Session()
        parsed = []
        
        response = session.post(f'https://vk.com/wkview.php',
                            data=f'act=show&al=1&dmcah=&loc=im&ref=&w=history{peer_id}_photo', cookies=cookies_final)
        
        response_json = json.loads(response.text[4:])
        
        try:
            last_offset = response_json['payload'][1][2]['offset']
            count_all = response_json['payload'][1][2]['count']
        except:
            last_offset = response_json['payload'][1][0]['offset']
            count_all = response_json['payload'][1][0]['count']

        while (len(parsed) < count + offset)  and (last_offset != count_all):
            response_json = json.loads(response.text[4:])
        
            try:
                last_offset = response_json['payload'][1][2]['offset']
            except:
                last_offset = response_json['payload'][1][0]['offset']

            photos_vk =  re.findall(r'<a href="/photo(\S*)?all=1"', response_json['payload'][1][1])
            mails =  re.findall(r"'(\S*)', {img: this ,", response_json['payload'][1][1])

            for photo, mail in zip(photos_vk, mails):
                if len(parsed) < offset:
                    parsed.append(photo)
                    continue
                
                response = session.post(f'https://vk.com/al_photos.php', cookies=cookies_final,
                            data=f'act=show&al=1&al_ad=0&dmcah=&gid=0&list={mail}&module=im&photo={photo}')
                
                response_json = json.loads(response.text[4:])
                photo_size = list(response_json['payload'][1][3][0].items())
                photo_size.reverse()
                
                for i in range(len(photo_size)):
                    if 'attached_tags' in photo_size[i][0]:
                        photo_size = photo_size[:i]
                        break
            
                parsed.append(photo_size) 

            response = session.post(f'https://vk.com/wkview.php', cookies=cookies_final,
                    data=f'act=show&al=1&offset={last_offset}&part=1&w=history{peer_id}_photo')
        
        return parsed[offset + 3 : offset + 3 + count]


Sieht es sperrig aus? Ja. Funktioniert es viel langsamer als die offizielle API? Ja. Aber wenn VC die letzte Möglichkeit zum Zugriff auf Nachrichten wegnimmt, können wir immer einen Ausweg finden.

Ich stelle auch fest, dass ich versucht habe, an allen Stellen in der Bibliothek Fehler hinzuzufügen, Ausnahmen mit Erklärungen sind möglich. Wenn Sie jedoch Ereignisse finden, die keine Erklärung enthalten, informieren Sie mich bitte darüber.
, , - , , , , , . , , .

?


Für diejenigen, die daran interessiert sind, was unter der Haube dieses Skripts geschieht, werde ich kurz auf die wichtigsten Punkte eingehen. Während der Autorisierung werden einfache Anforderungsanforderungen gestellt, die die Anmeldung des Benutzers simulieren, die sich je nach Art der Autorisierung nur geringfügig ändert. Nach erfolgreicher Anmeldung werden Cookies in einer Pickle-Datei gespeichert. Wenn Sie eine API über die Dokumentation anfordern, wird allen benutzerdefinierten Parametern "param_" hinzugefügt, dh der Offset-Wert wird zu param_offset. Außerdem wird der Hash-Code in der Anforderung übertragen, die im Daten-Hash-Attribut des Schaltflächen-Tags "Ausführen" enthalten ist. Soweit ich bemerkt habe, ist dieser Wert für jede Methode konstant.

Ich stelle auch einen wichtigen Punkt fest: Das Passwort wird in ANSI-Codierung gesendet, wobei die Zeichen des russischen Alphabets durch ein "%" getrennt sind, und dieser Code reicht aus, um eine solche Decodierung zu implementieren. Dies kann für einige Linux-Benutzer ein Problem sein, da diese Codierung meines Erachtens in Python unter diesem Betriebssystem nicht standardmäßig enthalten ist.

self.password = str(password.encode('ANSI')).replace('\\x', '%')[2:-1]

Eines der Probleme für mich war auch das seltsame Verhalten einiger Methoden. Wenn ich beispielsweise die Parameter vertauschte, konnte das Skript eine Antwort zurückgeben, die zehnmal kleiner als die angeforderte war, oder überhaupt nichts zurückgeben. Um dieses Problem zu lösen, habe ich mich entschlossen, die Parameter in einer strengen Reihenfolge zu analysieren und zu senden, wie in der Dokumentation angegeben. Vielleicht ist dies ein einfacher Zufall, aber nach diesen Problemen dieser Art hatte ich nicht:

response = session.get(f'https://vk.com/dev/{name}', cookies=self.cookies_final)
hash_data =  re.findall(r'data-hash="(\S*)"', response.text)

soup = BeautifulSoup(response.text, features="html.parser")
params = soup.findAll("div", {"class": "dev_const_param_name"})
params = [cleanhtml(str(i)) for i in params]

payload, checker = '', 0
for param in params:
   if param in kwargs:
       checker += 1
       payload += '&{}={}'.format('param_' + \
   param, quote(str(kwargs[param]) if type(kwargs[param]) != bool else str(int(kwargs[param]))))
        
if checker != len(kwargs):
   raise Exception_MessagesAPI('Some of the parametrs invalid', 'InvalidParameters')

Gesamt


Nun, für mich war diese Bibliothek die erste Erfahrung beim Schreiben "offener" Projekte, daher bitte ich Sie, sie nicht streng zu beurteilen. Ich wollte nur Menschen helfen, die das gleiche Problem wie ich haben: Einschränkung der Nachrichten-API. Ich möchte mich auch bei meinen Freunden bedanken, die mir geholfen haben, diesen Artikel zu schreiben und den Code zu testen.

All Articles