Je suis un nerd de limite VTuber. En décembre, "Virtual to Live" a eu lieu au Ryogoku Kokugikan, et il a également été distribué par Nico Nama. Le perdant ne pouvait pas aller sur le site et même le voir en temps réel, mais il ne pouvait pas attendre la sortie du disque. C'est pourquoi j'ai acheté un billet en ligne et vu le décalage horaire. Eh bien, mourons. Je voulais le sauvegarder avant de voir le décalage horaire, alors j'ai commencé à analyser Nikosei avec Python dans ma main.
Au fait, ma force est de savoir "qu'est-ce que web socket?", "Hls?", "Selenium?".
** * Remarque: informations en date du 17 décembre 2019. Les spécifications de Nico Nama peuvent changer à l'avenir. ** **
L'analyse a tiré parti des DevTools de Chrome. Vous pouvez l'afficher en appuyant sur «F12» sur Chrome. Tout d'abord, ouvrons une distribution appropriée. Si vous appuyez sur F12 après avoir ouvert la distribution, rechargez-la une fois.
HLS
Dans l'onglet Réseau
de DevTools, vous pouvez voir les journaux de communication de ce site Web. (Si vous cochez «Désactiver le cache», le journal ne disparaîtra pas même si la page se déplace)
Pour la distribution vidéo, j'ai recherché la communication avec le plus grand "Size".
Puis
https: // {???} .dmc.nico / hlsarchive / ht2_nicolive / nicolive-hamster- {Delivery ID} _main_ {16 nombre hexadécimal} / 4 / ts / {nombre} .ts?
Je téléchargeais une grande quantité de données à partir d'une URL comme celle-ci.
Le "{nombre}" semble augmenter dans l'ordre.
Et d'une URL similaire
https: // {???} .dmc.nico / hlsarchive / ht2_nicolive / nicolive-hamster- {ID de distribution} _main_ {16 nombre hexadécimal} / 4 / ts / playlist.m3u8?
Avec la réponse de
playlist
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:5
(Omission)
Des données comme celles-ci ont été envoyées.
Quand j'ai vérifié avec «.m3u8» et «.ts», il semble que ce soit un fichier utilisé par le protocole appelé «HLS (HTTP Live Streaming)».
Ce HLS
semble être facile à DL en utilisant ffmpeg
, donc tout ce que vous avez à faire est d'obtenir l'URL.
Où avez-vous obtenu cette URL?
Appuyez sur "Ctrl + F" dans l'onglet "Réseau" de DevTools.
Là, j'ai trouvé https: // {???}. Dmc.nico/hlsarchive/ht2_nicolive/nicolive-hamster-{Delivery ID} _main_ {16 nombre hexadécimal}
Cherchons en tapant.
Apparemment, ce qui sort est "communication vers URL", et "communication avec URL" est introuvable.
À la suite de divers essais tels que "Que voulez-vous dire?", Il semble que la communication dans le socket Web ne puisse pas être recherchée.
Alors, recherchez avec wss:
.
Ensuite, vous pouvez trouver 3 sockets Web.
4012 /
semble s'envoyer des données binaires. Laissez-le pour le moment.
websocket
reçoit les informations de discussion du serveur au format JSON. Vous pouvez également laisser cela.
Le socket Web restant (ici timeshift
) n'est pas évident à première vue. Cherchons l'URL que vous recherchez.
Puis il y a eu une communication à succès.
Il semble que cette communication reçoive JSON.
J'ai trouvé l'URL que je cherchais dans ʻuri` dans ce JSON.
Are you still watching? Eh bien, c'est naturel de dire ça, mais il semble que l'URL devient invalide lorsque le serveur juge que "je n'ai plus vu ce type". Par conséquent, il n'est pas possible de dire "obtenir l'URL et la laisser tranquille".
Alors, que dois-je faire ... j'ai eu l'idée
C'était une méthode pour «laisser Nico Nico».
Si vous laissez l'URL de distribution ouverte dans Selenium, le client enverra automatiquement "Je suis toujours en train de regarder" au serveur.
En attendant, ffmpeg
sera DL, ce qui est une histoire simple.
Cependant, cette méthode convient si vous venez de la télécharger, mais je pense qu'elle ne peut pas être utilisée quand il s'agit de "Je veux jouer avec ma propre application!", Donc dans ce cas, vous devez envoyer "Je suis toujours en train de regarder" par vous-même. Il y a.
De plus, Nico Nico ne peut voir la livraison que dans une fenêtre à la fois (je ne connais pas Nico Nico, donc je ne connais pas les détails).
Veuillez noter que si vous ouvrez la distribution pendant DL, DL s'arrêtera.
shell
python dl.py {live_id} {id} {pass}
Vous pouvez le faire avec.
dl.py
from selenium import webdriver
import chromedriver_binary
import json
import time
import sys
import subprocess
lid = sys.argv[1]
id = sys.argv[2]
pa = sys.argv[3]
options = webdriver.ChromeOptions()
options.add_argument('--headless')
caps = webdriver.DesiredCapabilities.CHROME
caps['goog:loggingPrefs'] = {'performance': 'ALL'}
driver = webdriver.Chrome(
options=options, desired_capabilities=caps, service_log_path='NUL')
driver.get('https://account.nicovideo.jp/login')
fid = driver.find_element_by_xpath('//*[@id="input__mailtel"]')
fpa = driver.find_element_by_xpath('//*[@id="input__password"]')
fid.clear()
fid.send_keys(id)
fpa.clear()
fpa.send_keys(pa)
fpa.submit()
driver.get('https://live2.nicovideo.jp/watch/' + lid)
# setting_button = driver.find_element_by_xpath(
# '/html/body/div/div/div[4]/div[3]/div/div/div[1]/div[3]/div[1]/div[2]/div[3]/button[4]').click()
# time.sleep(1)
# driver.find_element_by_xpath(
# '/html/body/div/div/div[4]/div[3]/div/div/div[1]/div[3]/div[1]/div[2]/div[4]/div/div/div[2]').click()
# time.sleep(1)
# driver.find_element_by_xpath(
# '/html/body/div/div/div[4]/div[3]/div/div/div[1]/div[3]/div[1]/div[2]/div[4]/section[2]/ul/div[2]').click()
# time.sleep(3)
# driver.find_element_by_xpath(
# '/html/body/div/div/div[4]/div[3]/div/div/div[1]/div[3]/div[1]/div[2]/button').click()
time.sleep(3)
log = [json.loads(i['message']) for i in driver.get_log(
'performance') if json.loads(i['message'])['message']['method'] == 'Network.webSocketFrameReceived']
log = [json.loads(i['message']['params']['response']['payloadData'])
for i in log if i['message']['params']['response']['payloadData'][0] == '{']
log = [i['body'] for i in log if 'body' in i.keys()]
uri = ''
quality = 6
for i in log:
if 'command' in i.keys():
if i['command'] == 'currentstream':
if 0 != i['currentStream']['qualityTypes'].index(i['currentStream']['quality']) < quality:
uri = i['currentStream']['uri']
quality = i['currentStream']['qualityTypes'].index(
i['currentStream']['quality'])
if quality == 0:
break
subprocess.run(['ffmpeg', '-i', uri, '-c', 'copy', 'output.mp4'])
driver.quit()
options = webdriver.ChromeOptions()
options.add_argument('--headless')
caps = webdriver.DesiredCapabilities.CHROME
caps['goog:loggingPrefs'] = {'performance': 'ALL'}
driver = webdriver.Chrome(
options=options, desired_capabilities=caps, service_log_path='NUL')
Vous pouvez l'exécuter sans afficher la fenêtre en ajoutant l'option --headless
.
caps ['goog: loggingPrefs'] = {'performance': 'ALL'}
est le paramètre pour voir le journal de communication.
Je ne suis pas sûr des détails.
driver.get('https://account.nicovideo.jp/login')
fid = driver.find_element_by_xpath('//*[@id="input__mailtel"]')
fpa = driver.find_element_by_xpath('//*[@id="input__password"]')
fid.clear()
fid.send_keys(id)
fpa.clear()
fpa.send_keys(pa)
fpa.submit()
Les étudiants Nico ne peuvent pas être vus sans se connecter. Par conséquent, connectez-vous ici une fois.
driver.get('https://live2.nicovideo.jp/watch/' + lid)
# setting_button = driver.find_element_by_xpath(
# '/html/body/div/div/div[4]/div[3]/div/div/div[1]/div[3]/div[1]/div[2]/div[3]/button[4]').click()
# time.sleep(1)
# driver.find_element_by_xpath(
# '/html/body/div/div/div[4]/div[3]/div/div/div[1]/div[3]/div[1]/div[2]/div[4]/div/div/div[2]').click()
# time.sleep(1)
# driver.find_element_by_xpath(
# '/html/body/div/div/div[4]/div[3]/div/div/div[1]/div[3]/div[1]/div[2]/div[4]/section[2]/ul/div[2]').click()
# time.sleep(3)
# driver.find_element_by_xpath(
# '/html/body/div/div/div[4]/div[3]/div/div/div[1]/div[3]/div[1]/div[2]/button').click()
time.sleep(3)
log = [json.loads(i['message']) for i in driver.get_log(
'performance') if json.loads(i['message'])['message']['method'] == 'Network.webSocketFrameReceived']
log = [json.loads(i['message']['params']['response']['payloadData'])
for i in log if i['message']['params']['response']['payloadData'][0] == '{']
log = [i['body'] for i in log if 'body' in i.keys()]
uri = ''
quality = 6
for i in log:
if 'command' in i.keys():
if i['command'] == 'currentstream':
if i['currentStream']['qualityTypes'].index(i['currentStream']['quality']) < quality:
uri = i['currentStream']['uri']
quality = i['currentStream']['qualityTypes'].index(
i['currentStream']['quality'])
if quality == 0:
break
Vous pouvez obtenir le journal des performances avec driver.get_log ('performance')
.
ʻSi json.loads (i ['message']) ['message'] ['method'] == 'Network.webSocketFrameReceived'extrait uniquement la réception de websocket. De là, nous ne laisserons que ceux qui sont au "format JSON et qui ont un" corps "". L'URL est enregistrée avec ʻuri = i ['currentStream'] ['uri']
.
Ici, j'essaye d'obtenir la "meilleure URL de qualité" de la communication.
Dans la partie commentaire, nous sélectionnons la vidéo de la plus haute qualité et arrêtons la vidéo.
J'ai commenté cela car il est très probable que cela change dans les futures mises à jour.
Nous vous recommandons d'étudier comment utiliser le sélénium et de l'écrire vous-même.
subprocess.run(['ffmpeg', '-i', uri, '-c', 'copy', 'output.mp4'])
driver.quit()
J'exécute ffmpeg
avec subprocess.run (['ffmpeg', '- i', uri, '- c', 'copy', 'output.mp4'])
.
ffmpeg
doit déjà être installé.
Fermez chrome avec'driver.quit () '.
Même si vous analysez un service Web et le publiez dans le monde entier, il devient souvent inutilisable en raison des modifications des spécifications du service. Cette fois, plutôt que de partager les "spécifications actuelles", j'ai voulu partager "N'est-il pas possible d'analyser les spécifications de cette manière?", Donc cet article a été rédigé ainsi. Comme je l'ai dit au début, je le partage sans rien savoir. Si vous pensez "est-ce étrange ici?", Veuillez le signaler.