Время прочтения: 7 мин.
Довольно часто о проблемах в той или иной сфере работы организации можно узнать на основании отзывов как клиентов и пользователей, так и самих сотрудников. Когда все сайты с отзывами и рекомендациями уже проверены, но информации все равно критически не хватает, возникает вопрос: «Что дальше? Какие еще источники информации использовать?». Решение есть – использовать информацию из популярных сообществ в социальных сетях! Сегодня более подробно остановимся на разработке приложения, позволяющего производить сбор по информации о постах и комментариях к ним в такой популярной социальной сети как «Вконтакте».
Для начала определимся со структурой, предоставляемой сообществами информации. Каждое сообщество имеет свой уникальный id (Как узнать id сообщества можно почитать в официальном FAQ: https://vk.com/faq18062.) Для каждого размещенного поста установлен свой id, также, как и в случае с комментариями к ним. Тип связи между таблицами будет «один ко многим». В данном типе связей несколько строк из дочерней таблицы (в нашем случае комментарии к публикациям) зависят от одной строки в родительской таблице (публикация). Представим структуру будущих таблиц с необходимыми нам данными в виде UML диаграммы:
Где:
postsInfo – таблица, хранящая информацию о публикациях сообществ
postID – id публикации в сообществе
groupID – id сообщества
postDate – дата размещения публикации
postText – текст публикации
postStatus – статус обработки публикации
commentsInfo – таблица, хранящая информацию о комментариях к публикациям сообществ
commentID – id комментариях к публикации
commentatorID – id пользователя, оставившего комментарий
postDate – дата размещения комментария
commentText – текст комментария к публикации
Многих может заинтересовать вопрос: «Для чего предназначено поле postStatus?». Данное поле было добавлено в ходе программной реализации, с целью отслеживания статуса обработки информации по публикациям, а также во избежание потери информации при возникновении исключений (при возникновении ошибки необходимо только перезапустить код и он продолжит обработку данных с момента «падения»)
После определения схемы хранения данных, приступим к программной реализации.
Импортируем необходимые для работы библиотеки:
import requests
from datetime import datetime as dt
import pyodbc
import os
import time
import re
Для отправки http-запросов к vk api будет использоваться библиотека requests, а запись полученных результатов будет осуществляться в базу данных посредством библиотеки pyodbc.
После импорта необходимых библиотек реализуем класс для обработки и записи получаемой информации в базу данных:
class DbClient:
def __init__(self,connectionString):
self.connectionString = connectionString
def createDb(self,conStrForCreateDb):
try:
# проверка на наличие директории для хранения базы данных
if not os.path.exists("C:\Databases"):
os.mkdir("C:\Databases") # создание директории в случае ее отсутствия
#создание подключения к БД
connection = pyodbc.connect(conStrForCreateDb,autocommit=True)
createDbRequest = """CREATE DATABASE YOUR_DATABASE_NAME ON PRIMARY (NAME=N' YOUR_DATABASE_NAME ',FILENAME=N'C:\\Databases\\ YOUR_DATABASE_NAME.mdf') LOG ON (NAME=N' YOUR_DATABASE_NAME _log',FILENAME=N'C:\\Databases\\ YOUR_DATABASE_NAME _log.ldf')"""
# создание объекта- курсора, посредством которого будут производиться запросы к таблицам
dbCursor = connection.cursor()
# выполнение скрипта на создание БД
dbCursor.execute(createDbRequest)
connection.commit()
connection.close()
print("Database creation completed...")
except Exception as error:
print("Method failed with error:" + str(error))
def createTables(self):
createTablesRequests = ["""CREATE TABLE postsInfo(groupID int,postID int,postDate datetime,postText nvarchar(max),postStatus bit)""",
"""CREATE TABLE commentsInfo(commentId int,commentatorId int,postID int,postDate datetime,commentText nvarchar(MAX),userStatus bit,textStatus bit)"""]
# итеративное прохождение списка sql-запросов и их выполнение для создания необходимых таблиц
for request in createTablesRequests:
connection = pyodbc.connect(self.connectionString)
dbCursor = connection.cursor()
dbCursor.execute(request)
connection.commit()
connection.close()
print("Creation of work tables is completed...")
def insertDataInPostInfo(self,postID,postDate,postText,groupID):
try:
sqlString = """IF NOT EXISTS(SELECT * FROM postsInfo WHERE postID=?) INSERT INTO postsInfo (groupID,postID,postDate,postText,postStatus) VALUES (?,?,?,?,0)"""
connection = pyodbc.connect(self.connectionString)
dbCursor = connection.cursor()
dbCursor.execute(sqlString,postID,groupID,postID,postDate,re.sub('\n',' ',postText))
connection.commit()
connection.close()
except Exception as error:
print(error)
def insertDataInCommentsInfo(self,commentId,commentatorId,postID,postDate,commentText):
try:
sqlString = """IF NOT EXISTS(SELECT * FROM commentsInfo WHERE commentId=?)
INSERT INTO commentsInfo (commentId,commentatorId,postID,postDate,commentText,userStatus,textStatus) VALUES(?,?,?,?,?,0,0)"""
connection = pyodbc.connect(connectionString)
dbCursor = connection.cursor()
dbCursor.execute(sqlString, commentId,commentId,commentatorId,postID,postDate,re.sub('\n',' ',commentText))
connection.commit()
connection.close()
except Exception as error:
print("Error in insertDataInCommentsInfo " +str(error))
def updatePostInfo(self, postID):
# запрос на изменение статуса публикации после выгрузки комментариев к нему
sqlString = """update postsInfo set postStatus=1 where postID=?"""
connection = pyodbc.connect(connectionString)
dbCursor = connection.cursor()
dbCursor.execute(sqlString,postID)
connection.commit()
connection.close()
При инициализации объекта класса DbClient в конструктор будет передана строка подключения к нашей базе. Данный класс содержит следующие методы:
createDb – служит для создания базы данных
createTables – создаёт необходимые для записи таблицы
insertDataInPostInfo – производит запись информации о публикации
insertDataInCommentsInfo – производит запись комментариев к публикации и информации о них
updatePostInfo – обновляет статус обработки данных публикации
Когда вопрос с записью и хранением данных закрыт, можно приступить к непосредственной разработке методов по их сбору.
class Vk:
def __init__(self,token,connectionString):
self.token = token
self.connectionString = connectionString
self.db = DbClient(connectionString) #создание экземпляра класса для работы с БД
def searchPosts(self,groupId):
offset= 0 # начальный индекс поиска публикаций
count= 95 #шаг продвижения индекса поиска публикаций
try:
for _ in range(0,4):
#формирование списка параметров запроса к api
params = {'owner_id':groupId,'offset':offset,'count':count,'filter':'all','extended':1,'access_token':self.token,'v':5.103}
#отправка запроса с заданными параметрами
r = requests.get('https://api.vk.com/method/wall.get',params)
for j in range(0,count):
#запись получаемых данных в таблицу
self.db.insertDataInPostInfo(r.json()['response']['items'][j]['id'], dt.fromtimestamp(r.json()['response']['items'][j]['date']).strftime('%Y%m%d %H:%M:%S'),r.json()['response']['items'][j]['text'],groupId)
offset += count # наращивание шага продвижения по публикациям
# принудительная «остановка» работы программы, для соблюдения требований api по количеству запрсов
time.sleep(2)
except Exception as error:
print(error)
def searchComments(self,groupId):
groupId = int(groupId)
# поиск публикаций, к которым еще не произведен поиск комментариев
sqlString = """select postID from postsInfo where groupID=? and postStatus=0"""
connection = pyodbc.connect(self.connectionString)
dbCursor = connection.cursor()
dbCursor.execute(sqlString,groupId)
for row in dbCursor:
self.searchPostComments(groupId,row.postID,self.token)
self.db.updatePostInfo(row.postID)
connection.commit()
connection.close()
time.sleep(2)
def searchPostComments(self,groupId,postID,userToken):
params = {'owner_id':groupId,'post_id':postID,'need_likes':0,'offset':0,'count':95,'preview_lenght':0,'extended':1,'access_token':userToken,'v':5.103}
r2 = requests.get('https://api.vk.com/method/wall.getComments',params)
commentsCount = int(r2.json()['response']['count'])
for i in range(0,commentsCount):
try:
countThreads = int(r2.json()['response']['items'][i]['thread']['count'])
commentId = int(r2.json()['response']['items'][i]['id'])
if countThreads !=0:
self.db.insertDataInCommentsInfo(int(r2.json()['response']['items'][i]['id']),
int(r2.json()['response']['items'][i]['from_id']),
int(postID),
dt.fromtimestamp(r2.json()['response']['items'][i]['date']).strftime('%Y%m%d %H:%M:%S'),
str(r2.json()['response']['items'][i]['text']))
self.writeThreadsComments(groupId,postID,commentId,userToken)
else:
self.db.insertDataInCommentsInfo(int(r2.json()['response']['items'][i]['id']),
int(r2.json()['response']['items'][i]['from_id']),
int(postID),
dt.fromtimestamp(r2.json()['response']['items'][i]['date']).strftime('%Y%m%d %H:%M:%S'),
str(r2.json()['response']['items'][i]['text']))
except Exception as error:
print("Error in searchComments"+str(error))
continue
time.sleep(2)
def writeThreadsComments(self,groupId,postId,commentId,userToken):
params = {'owner_id':groupId,'post_id':postId,'comment_id':commentId,'need_likes':0,'offset':0,'count':20,'preview_lenght':0,'extended':1,'access_token':userToken,'v':5.103}
r = requests.get('https://api.vk.com/method/wall.getComments',params)
commentsCount = int(r.json()['response']['count'])
for i in range(0,commentsCount):
try:
self.db.insertDataInCommentsInfo(int(r.json()['response']['items'][i]['id']),int(r.json()['response']['items'][i]['from_id']),int(postId),dt.fromtimestamp(r.json()['response']['items'][i]['date']).strftime('%Y%m%d %H:%M:%S'),str(r.json()['response']['items'][i]['text']))
except Exception as error:
print("Error in writeThreadsComments."+str(error))
continue
time.sleep(2)
searchPosts — производит поиск публикаций для заданного сообщества
searchComments – производит поиск еще необработанных публикаций, для которых необходимо произвести поиск комментариев
searchPostComments, writeThreadsComments – методы для поиска и записи комментариев к публикациям.
Для более удобной манипуляции с разработанными методами создадим небольшое консольное меню, реализовав класс Main
class Main:
def __init__(self,groupId,token,conStrForCreate,connectionString):
self.id = groupId
self.token = token
self.conStrForCreate = conStrForCreate
self.connectionString = connectionString
self.db1 = DbClient(conStrForCreate)
self.db2 = DbClient(connectionString)
self.vk = Vk(token,connectionString)
def menu(self):
p = ['1-Создать базу данных для работы','2-Создать необходимые таблицы',
'3-Произвести поиск постов (searchPosts)','4-Поиск комментариев к постам']
print('Выберите необходимое действие:\n{0}\n{1}\n{2}\n{3}\n{4}'.format(p[0],p[1],p[2],p[3]))
choice = str(input())
if choice=="createDb" or choice=="1":
self.db1.createDb(self.conStrForCreate)
if choice=="createTables" or choice=="2":
self.db2.createTables()
if choice=="searchPosts" or choice=="3":
self.vk.searchPosts(self.id)
if choice=="searchComments" or choice=="4":
self.vk.searchComments(self.id)
После создания всех необходимых
if __name__=="__main__":
token = 'YOUR_VK_API_TOKEN'
conStrForCreate = "Driver={SQL Server Native Client 11.0};Server=DESKTOP-R9VI2A2\SQLEXPRESS;Database=master;Trusted_Connection=yes;"#строка подключения для создания новой БД для работы
connectionString = "Driver={SQL Server Native Client 11.0};Server=DESKTOP-P\SQLEXPRESS;Database=YOUR_BD_NAME;Trusted_Connection=yes;"#строка подключения к используемой БД
m = Main(id,token,conStrForCreate,connectionString)
while(True):
m.menu()
В результате запуска получаем окно:
Результатом запуска программы являются созданные таблицы со списком публикаций и комментариев к ним: