Compare commits
103
Commits
0.0.1
..
3ca58b2466
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ca58b2466
|
||
|
|
20025e02b4
|
||
|
|
e493eaeb49
|
||
|
|
e1aff6f469
|
||
|
|
ff5542d8b0
|
||
|
|
65bc8853e6
|
||
|
|
d3c87d3b6f
|
||
|
|
58f25a114e
|
||
|
|
3bba97e90c
|
||
|
|
9d433ba2f3
|
||
|
|
572f9e121f
|
||
|
|
02c3d74eb6
|
||
|
|
95e96d08e1
|
||
|
|
df3af2780b
|
||
|
|
68cd27c263
|
||
|
|
f44ee4b3c1
|
||
|
|
7626c7bad1
|
||
|
|
76e41f2f7b
|
||
|
|
8d4f31d106
|
||
|
|
c0db4eb437
|
||
|
|
e23a0fc4c7
|
||
|
|
e5dd476490
|
||
|
|
fd75d27b99
|
||
|
|
cb7b68342a
|
||
|
|
e166fdb4e5
|
||
|
|
8d34cf33ae
|
||
|
|
09f7675bf7
|
||
|
|
4449d488c9
|
||
|
|
34c0d03587
|
||
|
|
8d20327b67
|
||
|
|
09c2a8eafe
|
||
|
|
9cd7c57e2f
|
||
|
|
94ad823e3b
|
||
|
|
454503271e
|
||
|
|
54fe40e588
|
||
|
|
8cf5698051
|
||
|
|
04c59eb025
|
||
|
|
9b792015fa
|
||
|
|
4508b12b6c
|
||
|
|
ea604cce40
|
||
|
|
01aac3f66e
|
||
|
|
d0dee043c6
|
||
|
|
d5c55e756a
|
||
|
|
50306aff5d
|
||
|
|
4fa433495b
|
||
|
|
a0ba474fcc
|
||
|
|
b4cb072770
|
||
|
|
fda8fc728e
|
||
|
|
82e597f205
|
||
|
|
03c2566a20
|
||
|
|
a116b29df9
|
||
|
|
1fe92ec2d0
|
||
|
|
411a3db465
|
||
|
|
08b465753d
|
||
|
|
45c5d8a940
|
||
|
|
668aa608ed
|
||
|
|
c746d43ddf
|
||
|
|
05cd243abd
|
||
|
|
9d40fef429
|
||
|
|
d34fade0cd
|
||
|
|
68b1f9a77f
|
||
|
|
c17a33d181
|
||
|
|
59dc5676cc
|
||
|
|
548a797ee8
|
||
|
|
46ef8146f8
|
||
|
|
4133faad41
|
||
|
|
fced9e0a3a
|
||
|
|
f114aac0cf
|
||
|
|
7456685f2b
|
||
|
|
270c48da02
|
||
|
|
f6c11b54f3
|
||
|
|
27cd5c3a64
|
||
|
|
eb805f5ced
|
||
|
|
d6f6b26361
|
||
|
|
b3779fe49a
|
||
|
|
3ae0f4e924
|
||
|
|
3758405f52
|
||
|
|
71e4bd95a9
|
||
|
|
34bbc216f6
|
||
|
|
baec8fcb6c
|
||
|
|
773f86c551
|
||
|
|
f436007836
|
||
|
|
dfbf38b071
|
||
|
|
292dd8919e
|
||
|
|
dab4c8ff69
|
||
|
|
9d5c9fde2a
|
||
|
|
f201ae7a91
|
||
|
|
4cae7e09d1
|
||
|
|
e4b4ce21a2
|
||
|
|
a2990c7699
|
||
|
|
923c14a77b
|
||
|
|
73a9dea023
|
||
|
|
938ae4b0fb
|
||
|
|
c50a82df1b
|
||
|
|
36f1fb9e83
|
||
|
|
934954092a
|
||
|
|
eaae954e1b
|
||
|
|
2ffc1d0e5d
|
||
|
|
53acda6abe
|
||
|
|
d776c09fec
|
||
|
|
daf14d4b5b
|
||
|
|
3aa9947f8e
|
||
|
|
c828b118d3
|
@@ -1,3 +1,5 @@
|
||||
A video introducing this project is available [here](https://crawler.yt.lemnoslife.com/presentation).
|
||||
|
||||
# The algorithm:
|
||||
|
||||
To retrieve the most YouTube video ids in order to retrieve the most video captions, we need to retrieve the most YouTube channels.
|
||||
@@ -9,6 +11,24 @@ A ready to be used by the end-user website instance of this project is hosted at
|
||||
|
||||
See more details on [the Wiki](https://gitea.lemnoslife.com/Benjamin_Loison/YouTube_captions_search_engine/wiki).
|
||||
|
||||
# The project structure:
|
||||
|
||||
- `main.cpp` contains the C++ multi-threaded algorithm proceeding to the YouTube channels discovery. It is notably made of the following functions:
|
||||
- `main` which takes into account the command line arguments, load variables from files (`channels.txt`, `keys.txt`, `channels/` content) and start the threads as executing `treatChannels` function
|
||||
- `treatChannels` gets a YouTube channel to treat, treat it in `treatChannelOrVideo` function and compress the retrieved data
|
||||
- `treatChannelOrVideo` which provided a YouTube channel id or a video id, treats this resource. In both cases it treats comments left on this resource. In the case of a channel it also treats its `CHANNELS`, `COMMUNITY`, `PLAYLISTS` and `LIVE` tabs and downloads the captions of the channel videos.
|
||||
- `markChannelAsRequiringTreatmentIfNeeded` which provided a YouTube channel id marks it as requiring treatment if it wasn't already treated
|
||||
- `execute` which provided an `yt-dlp` command executes it in a shell
|
||||
- `getJson` which provided an API request returns a JSON structure with its result. In the case that the API requested is YouTube Data API v3 and a set of keys is provided (see below `keys.txt`), it rotates the keys as required
|
||||
- `channels.txt` contains a starting set of channels which contains mostly the 100 most subscribed French channels
|
||||
- `keys.txt` contains a set of YouTube Data API v3 keys (not provided) to have the ability to request this API (see an alternative to filling it in the section below with `--no-keys` command line argument)
|
||||
- `scripts/` contains Python scripts to:
|
||||
- generate the `channels.txt` as described above (`retrieveTop100SubscribersFrance.py`)
|
||||
- remove channels being treated before a restart of the algorithm as described in [the `main` function documentation](https://gitea.lemnoslife.com/Benjamin_Loison/YouTube_captions_search_engine/src/commit/8dd89e6e881da0a905b6fa4b23775c4344dd0d9d/main.cpp#L126-L128) (`removeChannelsBeingTreated.py`)
|
||||
- `website/` is a PHP website using WebSocket to allow the end-user to proceed to requests on the retrieved dataset. When fetching the website, the end-user receives the interpreted `index.php` which upon making a request interacts with `websocket.php` which in the back-end dispatches the requests from various end-users to `search.py` (which treats the actual end-user request on the compressed dataset) by using `users/` to make the inter-process communication.
|
||||
|
||||
Note that this project heavily relies on [YouTube operational API](https://github.com/Benjamin-Loison/YouTube-operational-API) [which was modified for this project](https://gitea.lemnoslife.com/Benjamin_Loison/YouTube_captions_search_engine/wiki/YouTube-operational-API-commits).
|
||||
|
||||
# Running the YouTube graph discovery algorithm:
|
||||
|
||||
Because of [the current compression mechanism](https://gitea.lemnoslife.com/Benjamin_Loison/YouTube_captions_search_engine/issues/30), Linux is the only known OS able to run this algorithm.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
PREFIX = 'Comments per second: '
|
||||
PREFIX = 'Channels per second: '
|
||||
alreadyTreatedCommentsCount = 0
|
||||
|
||||
with open('nohup.out') as f:
|
||||
@@ -8,5 +8,7 @@ with open('nohup.out') as f:
|
||||
for line in lines:
|
||||
if PREFIX in line:
|
||||
alreadyTreatedCommentsCount += int(line.split(PREFIX)[-1])
|
||||
#if 'UCsT0YIqwnpJCM-mx7-gSA4Q' in line:
|
||||
# break
|
||||
|
||||
print(alreadyTreatedCommentsCount)
|
||||
+8
-4
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
# This algorithm should also take in account other features that we use to retrieve channels.
|
||||
|
||||
import os, requests, json, time, datetime
|
||||
|
||||
path = 'channels/'
|
||||
@@ -12,11 +14,13 @@ def getTimestampFromDateString(dateString):
|
||||
for channelId in list(os.walk('.'))[1]:
|
||||
channelId = channelId[2:]
|
||||
#print(channelId)
|
||||
numberOfRequests = len(list(os.walk(channelId))[0][2])
|
||||
numberOfRequests = len(list(os.walk(f'{channelId}/requests'))[0][2]) - 1
|
||||
# Assume that the folder isn't empty (may not be the case, but it is most of the time).
|
||||
with open(f'{channelId}/{str(numberOfRequests - 1)}.json') as f:
|
||||
content = "\n".join(f.read().splitlines()[1:])
|
||||
data = json.loads(content)
|
||||
filePath = f'{channelId}/requests/{str(numberOfRequests - 1)}.json'
|
||||
with open(filePath) as f:
|
||||
print(filePath)
|
||||
#content = "\n".join(f.read().splitlines()[1:])
|
||||
data = json.load(f)#json.loads(content)
|
||||
snippet = data['items'][-1]['snippet']
|
||||
if 'topLevelComment' in snippet:
|
||||
snippet = snippet['topLevelComment']['snippet']
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
import os, requests, json
|
||||
|
||||
channelIds = next(os.walk('channels/'))[1]
|
||||
channelIds = [channelId.replace('.zip', '') for channelId in next(os.walk('channels/'))[2]]
|
||||
maxResults = 50
|
||||
|
||||
channelIdsChunks = [channelIds[i : i + maxResults] for i in range(0, len(channelIds), maxResults)]
|
||||
@@ -1,4 +1,7 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
# We can't proceed automatically by using `requests` Python module because https://socialblade.com/youtube/top/country/fr/mostsubscribed is protected by CloudFlare.
|
||||
# Note that `undetected-chromedriver` might be a workaround this limitation.
|
||||
|
||||
with open('mostsubscribed.html') as f:
|
||||
lines = f.read().splitlines()
|
||||
+18
-4
@@ -10,6 +10,11 @@ pathSearchMessageParts = sys.argv[2].split(' ')
|
||||
pathSearch = pathSearchMessageParts[1]
|
||||
message = ' '.join(pathSearchMessageParts[2:])
|
||||
|
||||
pathSearchRegex = re.compile(pathSearch)
|
||||
messageRegex = re.compile(message)
|
||||
|
||||
isPathSearchAChannelId = re.match(r'[a-zA-Z0-9-_]{24}', pathSearch)
|
||||
|
||||
searchOnlyCaptions = pathSearchMessageParts[0] == 'search-only-captions'
|
||||
|
||||
clientFilePath = f'users/{clientId}.txt'
|
||||
@@ -33,23 +38,31 @@ def cleanCaption(caption):
|
||||
return caption.replace('\n', ' ')
|
||||
|
||||
# As `zipgrep` doesn't support arguments to stop on first match for each file, we proceed manually to keep a good theoretical complexity.
|
||||
files = [file for file in os.listdir(path) if file.endswith('.zip')]
|
||||
if isPathSearchAChannelId:
|
||||
file = pathSearch + '.zip'
|
||||
if os.path.isfile(path + file):
|
||||
files = [file]
|
||||
else:
|
||||
write(f'progress:0 / 0')
|
||||
else:
|
||||
files = [file for file in os.listdir(path) if file.endswith('.zip')]
|
||||
|
||||
for fileIndex, file in enumerate(files):
|
||||
write(f'progress:{fileIndex + 1} / {len(files)}')
|
||||
write(f'progress:{fileIndex} / {len(files)}')
|
||||
zip = zipfile.ZipFile(path + file)
|
||||
for fileInZip in zip.namelist():
|
||||
endsWithVtt = fileInZip.endswith('.vtt')
|
||||
if searchOnlyCaptions and not endsWithVtt:
|
||||
continue
|
||||
toWrite = f'{file}/{fileInZip}'
|
||||
if not bool(re.search(pathSearch, toWrite)):
|
||||
if not bool(pathSearchRegex.search(toWrite)):
|
||||
continue
|
||||
with zip.open(fileInZip) as f:
|
||||
if endsWithVtt:
|
||||
content = f.read().decode('utf-8')
|
||||
stringIOf = StringIO(content)
|
||||
wholeCaption = ' '.join([cleanCaption(caption.text) for caption in webvtt.read_buffer(stringIOf)])
|
||||
messagePositions = [m.start() for m in re.finditer(message, wholeCaption)]
|
||||
messagePositions = [m.start() for m in messageRegex.finditer(wholeCaption)]
|
||||
if messagePositions != []:
|
||||
timestamps = []
|
||||
for messagePosition in messagePositions:
|
||||
@@ -67,6 +80,7 @@ for fileIndex, file in enumerate(files):
|
||||
if message in str(line):
|
||||
write(toWrite)
|
||||
break
|
||||
write(f'progress:{fileIndex + 1} / {len(files)}')
|
||||
|
||||
with open(clientFilePath) as f:
|
||||
while True:
|
||||
|
||||
Reference in New Issue
Block a user