-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPackParser.py
184 lines (165 loc) · 12.4 KB
/
PackParser.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
import json, sqlite3, os
from requests import get
from datetime import datetime
from re import findall
from time import time
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) # Не на всех машинах работают запросы по HTTPS (Entware в частности), поэтому используется HTTP. Вообще, это крайне не рекомендуется из соображений безопасности.
requestHTTPSVerify = True # Если хотите использовать HTTPS, смените на True. Если выходят ошибки urllib3, попробуйте сменить на False
### Блок 0. Создание БД(при отсутствии), подключение к БД, создание функции для добавления элементов в базу по заданному формату
conn = sqlite3.connect(os.path.join(os.getcwd(), "PDB.db"))
crsr = conn.cursor()
try:
crsr.execute("""SELECT * FROM pack WHERE pack_ID = 0""")
except sqlite3.OperationalError:
Update = False
count = 1 # Отсчёт для запросов к VK
packCount = 0 # Отсчёт паков для статистики
crsr.execute("""CREATE TABLE "pack" (
"pack_ID" INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
"name" TEXT NOT NULL,
"publisher_ID" INTEGER NOT NULL,
"date" INTEGER NOT NULL,
"source" TEXT NOT NULL,
"link" TEXT NOT NULL,
"tags" TEXT,
"isDownloaded" INTEGER
);""")
else:
Update = True
packCount = json.loads(crsr.execute("SELECT tags FROM pack WHERE pack_ID = 0").fetchall()[0][0])["Parsed"] # Получение количества пакетов за предыдущее обновление
count = json.loads(crsr.execute("SELECT tags FROM pack WHERE pack_ID = 0").fetchall()[0][0])["Answers"] # Получение общего количества ответов за предыдущее обновление
def AddToBase(pack_ID, name, publisher_ID, date, source, link, tags): # name, creator.ID, date, src, link, tags
crsr.execute("""INSERT INTO pack VALUES ({}, "{}", {}, {}, "{}", "{}", '{}', 0);""".format(pack_ID, name, publisher_ID, date, source, link, tags))
### Блок 1. Подготовка параметров, использующихся в get-запросах.
URL_START = "https://api.vk.com/method/"
URL_METHOD = "board.getComments?" # Информация по методу - https://vk.com/dev/board.getComments
URL_PARAMS = {"v": 5.103,
"access_token": "", # Для парсинга вам необходим токен ВК. Получить его вы можете здесь(Рекомендую использовать токен Kate Mobile): https://vkhost.github.io/
"group_id": 135725718,
"topic_id": 34975471,
"offset": 1,
"count": 100,
"extended": 1}
if len(URL_PARAMS["access_token"]) != 85: # Проверка токена на длину. По умолчанию длина токена с максимальными правами у ВК - 85.
raise Exception("No valid VK Token found. Please, check line #47 of code.")
### Блок 2. Получение общего количества ответов на обсуждение - [target].
URL_PARAM = []
for i in URL_PARAMS:
URL_PARAM.append("{}={}".format(i, URL_PARAMS[i]))
URL_END = "&".join(URL_PARAM)
try:
target = json.loads(get(URL_START + URL_METHOD + URL_END, verify = requestHTTPSVerify).text)["response"]["count"]
except KeyError:
raise Exception("User authorization failed: invalid session. Change token.")
### Блок 3. Получение всех ответов и запись в переменную [result].
while count <= target:
if Update:
URL_PARAMS["offset"] = count
URL_PARAM = []
for i in URL_PARAMS:
URL_PARAM.append("{}={}".format(i, URL_PARAMS[i]))
URL_END = "&".join(URL_PARAM)
try:
res = json.loads(get(URL_START + URL_METHOD + URL_END).text)["response"]["items"] # Переопределение в данном случае нужно для обновления информации. Без него, в теории, вы будете получать лишь первые 100 ответов [target/100] раз
result += res
except NameError:
result = json.loads(get(URL_START + URL_METHOD + URL_END).text)["response"]["items"]
URL_PARAMS["offset"] += 100
count += 100
if (
int(count*100/target) % 10 == 0
and ( (count*100/target) - int(count*100/target) ) < 0.5
):
print("Спаршено {}% ответов в обсуждении".format(round(count*100/target)))
print("Начато занесение в базу.")
### Блок 4. Фильтрация ответов.
if count == target:
print("Количество ответов в обсуждении не изменилось. Нечего получать.\n")
else:
for discussionReply in result:
try:
discussionReply["attachments"] # Попытка получить прикреплённые документы
except KeyError: # Поиск ссылок на сторонние ресурсы в случае отсутствия прикреплённых документов. Легче всего получать паки с помощью API, однако ни гугл, ни яндекс не дают доступ к API без авторизации. Данная проблема решена парсингом веб-страницы с пакетом
if discussionReply["text"].find("yadi.sk") > -1: #Получение ссылок c ЯД
if discussionReply["text"].find("\n") > -1 or discussionReply["text"].find(" ") > -1: # Фильтрация на наличие пробелов и переносов строки. На случай, если ссылок будет больше, чем 1
tempList = findall("yadi.sk/d/[\w\-]*", discussionReply["text"])
for t in tempList:
try:
searchResult = findall("""<div class="file-name">[\w ]+.\w+</div>""", get("https://" + t).text)[0]
except IndexError:
pass
else:
try:
discussionReply["owner_id"]
except Exception:
pass
else:
packCount += 1
name = searchResult[23:len(searchResult)-6] # Получение названия документа исходя из разметки веб-страницы. Получено опытным путём; при обновлении ресурсов, где хранятся паки, может не сработать.
AddToBase(packCount, # Последовательный номер пакета в базе
name, # Название документа
discussionReply['from_id'], # ID пользователя ВК, опубликовавшего пакет
discussionReply["date"], # Для получения стандартного времени(ДД.ММ.ГГГГ ЧЧ:ММ:СС) вместо эпохи Unix, используйте конструкцию datetime.utcfromtimestamp(i["date"]).strftime('%d.%m.%Y %H:%M:%S')
"Yandex.Disk", # В принципе, для оптимизации базы можно убрать поле источника базы. Лично я считаю, что это важно для статистики, но саму статистику я веду лишь на будущее.
t, # Ссылка на документ
"") # Пустое поле для тегов. Можно использовать в качестве тегов фильтр по названию, а можно вытаскивать темы из пака - файлы .siq представляют из себя простые архивы, а содержимое пака регулируется файлом context.xml, где хранятся названия раундов и категорий. Фильтруя названия категорий, можно получить полноценное тегирование, но для этого нужно качать пак целиком. Решение данной проблемы на 25.03.2020 не найдено
elif discussionReply["text"].find("drive.google") != -1: # Получение ссылок с Google Drive. Конструкция аналогична получению ссылок с ЯД
if discussionReply["text"].find("\n") > -1 or discussionReply["text"].find(" ") > -1:
tempList = findall("drive.google.com/open?id=[\w\-]*", discussionReply["text"])
if len(tempList) == 0:
tempList = findall("drive.google.com/file/d/[\w\-]*", discussionReply["text"])
for t in tempList:
try:
searchResult = findall("""<meta property="og:title" content="\w+.\w+">""", get("https://" + t).text)[0]
except IndexError:
pass
else:
packCount += 1
name = searchResult[35:len(searchResult)-2]
AddToBase(packCount,
name,
discussionReply['from_id'],
discussionReply["date"],
"Google Drive",
discussionReply,
"")
else:
for attachment in discussionReply["attachments"]:
try:
file = attachment["doc"] # Попытка получить список документов
except KeyError:
pass # Действие при отсутствии документов
else:
if file["ext"] == "siq": # Проверка документа на необходимое расширение
packCount += 1
if file['title'].find('.siq') == -1:
file['title'] += '.siq'
try:
AddToBase(packCount,
file['title'],
file['owner_id'],
file["date"],
"VK",
file['url'],
"")
except Exception as error:
print(file + "\n" + error)
else:
pass
# Окончание работы, сохранение и вывод статистики
if Update:
tags = json.loads(crsr.execute("SELECT tags FROM pack WHERE pack_ID = 0").fetchall()[0][0])
tags["Parsed"] = packCount
tags["Answers"] = target
crsr.execute("""UPDATE pack SET tags = '{}', date = {} WHERE pack_ID = 0;""".format(json.dumps(tags), round(time()))) # Обновление информации о положении базы
else:
tags = json.loads("""{"Parsed": 0,"Answers": 0}""")
tags["Parsed"] = packCount
tags["Answers"] = target
AddToBase(0, "INFO", 257018408, round(time()), "SIPP", "", json.dumps(tags)) # Добавление информации о текущем положении базы в качестве нулевого элемента при отсутствии таковой. Используется для обновления базы вместо полного получения всех паков заново
conn.commit()
conn.close()
print("В базе теперь {} паков. Общее количество ответов: {}".format(packCount, target))