Парсинг новостей групп VK

В продолжение предыдущей статьи, появилась необходимость парсить так-же и новости в социальной сети vk с проверкой на наличие стоп-слов. Для этого воспользовался модулем vk на python. Так-же понадобится токен доступа полученный на https://vk.com/apps?act=manage

В итоге код получился примерно следующий:

#!/usr/bin/env python3
# encoding: utf-8
import vk
import json
import funcs

with open('config.json', 'r') as file:
    config_data = json.load(file)
    print(config_data)


api = vk.API(access_token=config_data["vk_api_token"],v='5.131')

for group in config_data["groups"]:
    chan_data = funcs.get_chan_json(group)
    skeep_after = chan_data["las_id"]
    wall_content = api.wall.get(domain=group, count=config_data["limit"])
    poz = 0
    for message in wall_content["items"]:
        #print(message)
        if poz == 0:
            chan_data["las_id"] = message["id"]
            funcs.save_chan_json(group, chan_data)
        if skeep_after == message["id"]:
            print("Все новости уже прочитаны...")
            break
        for word in config_data["alert_words"]:
          if word in message["text"]:
              print(f"--нашли слово {word}")
              funcs.SendMailVK(config_data,group, word, message)
              print(message)
        poz = poz + 1
print("all done..");

По сути код очень простой — получаем через API VK все последние новости из каждой группы. Если в тексте новости находим стоп-слово, то отправляем соответствующее письмо. Так-же использую дополнительный файл функций, которые далее использую во всех парсерах:

#!/usr/bin/env python3
#encoding: utf-8
import json
from datetime import datetime
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from telethon.tl.functions.contacts import ResolveUsernameRequest
from telethon.tl.functions.channels import GetMessagesRequest
from telethon.tl.functions.messages import GetHistoryRequest, ReadHistoryRequest

def save_chan_json(chan,chan_data):
    f = open('saves/'+chan + '.json', "w+")
    json.dump(chan_data, f)
    f.close()

def get_chan_json(chan):
    # узнаём какое последнее сообщение прочитали на канале?
    chan_data = {}
    chan_data["las_id"] = 0
    try:
        with open('saves/'+chan + '.json', 'r') as file:
            chan_data = json.load(file)
            print(chan_data)
            return chan_data
    except:
        save_chan_json(chan, chan_data)
        return chan_data
    return chan_data;


def SendMail(config_data,chan,word,message):
    msg = MIMEMultipart()
    msg['Subject'] = f"Найдено слово '{word}' в новости на канале {chan} в Телеграм"
    msg.add_header('Content-Type', 'text/html')
    message.text=message.text.replace(word,"<strong>"+word+"</strong>")
    dt_pub=message.date.strftime('%d-%m-%Y %H:%M:%S')
    msg.set_payload(f"Канал: <a href='https://vc.com/{chan}'>https://t.me/{chan}</a>, опубликовано {dt_pub}<hr/>"+message.text)

    smtpObj = smtplib.SMTP(config_data["smtp_server"], config_data["smtp_port"])
    smtpObj.starttls()
    smtpObj.login(config_data["email_login"], config_data["from_password"])
    smtpObj.sendmail(config_data["email_from"], config_data["notify_email"], msg.as_string().encode('utf-8'))
    smtpObj.quit()

def SendMailVK(config_data,chan,word,message):
    msg = MIMEMultipart()
    msg['Subject'] = f"Найдено слово '{word}' в новости на группе {chan} в VK"
    msg.add_header('Content-Type', 'text/html')
    message["text"]=message["text"].replace(word,"<strong>"+word+"</strong>")
    dt_pub=datetime.utcfromtimestamp(message["date"]).strftime('%d-%m-%Y %H:%M:%S')
    msg.set_payload(f"Группа: <a href='https://vc.com/{chan}'>https://vk.com/{chan}</a>, опубликовано {dt_pub}<hr/>"+message["text"])

    smtpObj = smtplib.SMTP(config_data["smtp_server"], config_data["smtp_port"])
    smtpObj.starttls()
    smtpObj.login(config_data["email_login"], config_data["from_password"])
    smtpObj.sendmail(config_data["email_from"], config_data["notify_email"], msg.as_string().encode('utf-8'))
    smtpObj.quit()

def SendMailNews(config_data,url,word,message):
    msg = MIMEMultipart()
    msg['Subject'] = f"Найдено слово '{word}' в новости на сайте {url}"
    msg.add_header('Content-Type', 'text/html')
    if message.get("href")!=None:
     message.string="<a href='"+message["href"]+"'>"+message.string.replace(word,"<strong>"+word+"</strong>")+"</a>"
    else:
        message.string = message.string.replace(word,"<strong>" + word + "</strong>")
    msg.set_payload(message.string)

    smtpObj = smtplib.SMTP(config_data["smtp_server"], config_data["smtp_port"])
    smtpObj.starttls()
    smtpObj.login(config_data["email_login"], config_data["from_password"])
    smtpObj.sendmail(config_data["email_from"], config_data["notify_email"], msg.as_string().encode('utf-8'))
    smtpObj.quit()

Консоль администрирования 1С под Linux

Перейдя с Windows на Linux, теряется возможность управлять сервером 1С Предприятие через консоль администрирования, через оснастку. Но! оказывается довольно давно уже в платформе зашита подобная же утилита, которая доступна через «Функции для технического специалиста»: Стандартные -> Управление серверами.

При запуске будет практически аналогичная функциональность доступная ранее через консоль:

1С: Фоновые задания внутри фоновых заданий

Задача: распаралелить медленную вставку записей в БД 1С, при чтении файла Excel

Как ни странно, но такая возможность есть и работает начиная с версии платформы 8.3.8. Т.е. можно запустить фоновое задание внутри фонового задания….ну например по вставке в БД большого количества записей, которая выполняется весьма медленно. Полноценно этот процесс распаралелить можно например как-то так:

Функция СделатьЗаписьвБДПДЗФоново(парам) экспорт
	...
    // делаем медленную запись в БД
	...
конецфункции	

// Просматриваем массив с идентификаторами фоновых заданий и удаляем те которые завершились
Функция ПочиститьМассивОтЗавершенныхЗаданий(МассивФоновыхЗаданий) экспорт
		для каждого ФЗМ из МассивФоновыхЗаданий цикл
			ФЗ = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(ФЗМ);
			если ФЗ.Состояние<>СостояниеФоновогоЗадания.Активно тогда
				МассивФоновыхЗаданий.Удалить(МассивФоновыхЗаданий.Найти(ФЗМ));
			конецесли;	
		конеццикла;		
	возврат МассивФоновыхЗаданий;
конецфункции		

// выполняем загрузку построчно файла Excel, запуская запись данных в БД
// отдельным потоком. Количество потоков ограничивам 10 штуками
Функция ВыполнитьЗагрузку(парам) экспорт
	МассивФоновыхЗаданий=Новый Массив();
	МаксимумЗаданий=10;
...
    Для	 нСтрокаТФ = 2 ПО КолВоСтрокФайла Цикл  
        ....
			МассивПараметров = Новый Массив;
			Параметры=Новый Структура("aa,bb,cc",
				1,
				2,
				3,
            );
			МассивПараметров.Добавить(Параметры);
			ФЗ = ФоновыеЗадания.Выполнить("ДлительныеОперации.СделатьЗаписьвБДПДЗФоново",МассивПараметров);	
			МассивФоновыхЗаданий.Добавить(ФЗ.УникальныйИдентификатор);											
			МассивФоновыхЗаданий=ПочиститьМассивОтЗавершенныхЗаданий(МассивФоновыхЗаданий);
			// ждём пока завершаться запущеные потоки, если их больше 10
			пока МассивФоновыхЗаданий.Количество()>МаксимумЗаданий цикл
				МассивФоновыхЗаданий=ПочиститьМассивОтЗавершенныхЗаданий(МассивФоновыхЗаданий);
			конеццикла;	        
        ....
   конеццикла;
конецфункции	   

В ниже приведённом каркасе кода, фоново выполняется функция ВыполнитьЗагрузку(), которая читает большой файл Excel, затем каждый цикл вставки записи в БД запускает в свою очередь фоново, ограничивая в данном случае количество фоновых заданий 10 штуками.

Спам звонки от платформы uchi.ru

Вот не писал бы, если бы не припекло. Бесконечно звонят, предлагают всякие услуги купить. Причем звонят не роботы, а люди. Каждый раз прошу и вежливо и руганью удалить меня из своих БД и больше не звонить НИКОГДА. Не понимают..

Да, зарегистрирован на сайте. Но не сам, а принудительно в школе. И нигде своего согласия не давал на спам звонки. И возможности отозвать у них «согласие» на сайте нет. И учётку удалить тоже нет возможности.

спам звонки

1C: фоновая загрузка большого файла Excel в табличный документ

Задача: загрузить в табличный документ на форме файл большого размера, с индикацией прогресса загрузки с использованием фоновой работы.

Решение: если поддержка фоновой работы в 1С была уже довольно давно, то асинхронная загрузка файлов на сервер появилась лишь начиная с версии 8.3.15.1489 ,Ну тоже уже давно, но руки добрались начать использовать только сейчас, т.к. ранее было не критично — не настолько большие файлы загружал/обрабатывал.

Итак, сначала на форме разместим индикатор загрузки. Для этого в реквизитах формы необходимо создать переменную типа «число», и перетащив её на форму выбрать тип «индикатор»:

индикатор загрузки
индикатор загрузки
индикатор загрузки

На кнопку «Загрузить ЛС» навесим открытие диалогового окна:

	Фильтр = "Файл с лицевыми счетами(*.xlsx)|*.xlsx";
	ПараметрыДиалога = новый ПараметрыДиалогаПомещенияФайлов("Выберите файлы XLSX", Истина, Фильтр);		

А чуть ниже определим обработчики оповещения о ходе загрузки файла на сервер и окончании загрузки файла, которые укажем при вызове процедуры «НачатьПомещениеФайлаНаСервер» (есть еще и «НачатьПомещениеФайловНаСервер»). В итоге код получится такой:

&НаКлиенте
Процедура ЗагрузитьЛС(Команда)
	этаформа.ИндикаторЗагрузкиЛС=0;				
	Фильтр = "Файл с лицевыми счетами(*.xlsx)|*.xlsx";
	ПараметрыДиалога = новый ПараметрыДиалогаПомещенияФайлов("Выберите файлы XLSX", Истина, Фильтр);		
	
	ОповещениеОЗавершении = новый ОписаниеОповещения("ПослеЗагрузкиФайлаЛС", ЭтаФорма);		
	ОповещениеОХодеЗагрузки = новый ОписаниеОповещения("ОповещениеОХодеЗагрузкиЛС", ЭтаФорма);		
	
	Этаформа.Элементы.ПояснениеКЗагрузкеЛС.Заголовок="Перемещаю файл на сервер..";
	НачатьПомещениеФайлаНаСервер(ОповещениеОЗавершении,ОповещениеОХодеЗагрузки,,,ПараметрыДиалога, УникальныйИдентификатор);								                                                 

КонецПроцедуры

&НаКлиенте
Процедура ПослеЗагрузкиФайлаЛС (ОписаниеФайла, ДопПараметры) Экспорт
	Этаформа.Элементы.ПояснениеКЗагрузкеЛС.Заголовок="Файл загружен, обрабатываю..";
	этаформа.ИндикаторЗагрузкиЛС=0;				
КонецПроцедуры	

&НаКлиенте
Процедура ОповещениеОХодеЗагрузкиЛС (ПомещаемыйФайл, Помещено, ОтказОтПомещенияФайла,ДополнительныеПараметры) Экспорт
	этаформа.ИндикаторЗагрузкиЛС=Помещено;				
КонецПроцедуры	

В результате чего после выбора файла, по экрану побежит индикатор хода перемещения файла на сервер. Далее этот файл необходимо будет обработать на сервере фоново. И тут возникает один нюанс: мы не можем передать в фоновое задание ссылку на перемещенный файл во временном хранилище.Точнее можем, но фоновое задание это хранилище прочитать не может (это то ли глюк, то ли фича платформы — не понятно). Проблема.. Тогда делаем финт ушами: перед уходом в «фон», мы создадим временный файл во временной папке пользователя 1С, и передадим в фон уже не ссылку на него, а непосредственно имя временного файла. Для этого я просто написал функцию, которая на входе получает адрес загруженного файла, а на выходе даёт имя временного файла:

&НаСервере
Функция СохранитьФайлНаСервере(АдресВременногоХранилища,расш)
		ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресВременногоХранилища);
	    ИмяВременногоФайлаХар = ПолучитьИмяВременногоФайла(расш);
	    ДвоичныеДанные.Записать(ИмяВременногоФайлаХар); 
		возврат ИмяВременногоФайлаХар;	
КонецФункции

Кроме того, чтобы мы могли передать результат в обработку по завершению фоновой работы, нам необходимо при запуске фонового задания передать в него некоторые параметры, а именно:

  • имя созданного временного файла
  • колонки/строки откуда брать данные из эксель файла
  • адрес временного хранилища, куда поместить результат работы фонового задания
&НаКлиенте
Процедура ПослеЗагрузкиФайлаЛС (ОписаниеФайла, ДопПараметры) Экспорт
	Этаформа.Элементы.ПояснениеКЗагрузкеЛС.Заголовок="Файл загружен, обрабатываю..";
	этаформа.ИндикаторЗагрузкиЛС=0;				
	инф=Новый Структура("ВременныйФайл,Расширение,ЛС_начало,ЛС_лс,ЛС_то,ЛС_дата_установки_пу,АдресВременногХранилища");
	инф.ВременныйФайл=СохранитьФайлНаСервере(ОписаниеФайла.адрес,ОписаниеФайла.ссылканафайл.расширение);
	инф.Расширение=ОписаниеФайла.ссылканафайл.расширение;
	инф.ЛС_начало=объект.ЛС_начало;
	инф.ЛС_лс=объект.ЛС_лс;
	инф.ЛС_то=объект.ЛС_то;
	инф.ЛС_дата_установки_пу=объект.ЛС_дата_установки_пу;
	инф.АдресВременногХранилища=объект.ВрХранилищеФормы;	
КонецПроцедуры	

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
	объект.ВрХранилищеФормы=ПоместитьВоВременноеХранилище(0);
	ЗапуститьФЗЗагрузкиЛС(инф);	
ПодключитьОбработчикОжидания("ИндикаторВыполненияЗагрузкиФайловЛС",1,ложь);				
КонецПроцедуры

Функция которая будет работать фоново, должна размещаться в общем модуле. Это небольшой недостаток внешних обработок — фоново запускаются только процедуры-функции созданные внутри конфигурации. Но! до этого нам нужно опять же написать «обвязку» фонового задания, дабы мы имели возможность знать, работает или нет оно, а так-же на каком этапе. При запуске фонового задания, мы получаем идентификатор этого задания:

	Функция ЗапуститьФЗЗагрузкиЛС(Параметры)		
	МассивПараметров = Новый Массив;
	МассивПараметров.Добавить(Параметры);
	ФЗ = ФоновыеЗадания.Выполнить("СК_ГР_ДлительныеОперации.ЗагрузитьФайлыЛС",МассивПараметров);	
	объект.ЛС_ФЗ = ФЗ.УникальныйИдентификатор;					
КонецФункции  

Который в дальнейшем будем использоваться для того чтобы «узнать» как поживает собственно это задание, подключив на клиенте обработчик ожидания, выполняющийся раз в секунду:

&НаКлиенте
Процедура ИндикаторВыполненияЗагрузкиФайловЛС() Экспорт  
	пр=КакДелаУФЗЗагрузкиФайловЛС();   	
	если пр=неопределено тогда  
		этаформа.ИндикаторЗагрузкиЛС=100;
		ОтключитьОбработчикОжидания("ИндикаторВыполненияЗагрузкиФайловЛС");
		Этаформа.Элементы.ПояснениеКЗагрузкеЛС.Заголовок="Файлы обработаны";
	иначе
		этаформа.ИндикаторЗагрузкиЛС=пр;         		
	конецесли;
КонецПроцедуры	 
&НаСервере
Функция КакДелаУФЗЗагрузкиФайловЛС()
	ФЗ = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(объект.идФЗ);
	если ФЗ=Неопределено тогда		
		возврат неопределено;
	иначе	                                                                                                    
		если ФЗ.Состояние=СостояниеФоновогоЗадания.Завершено тогда
			возврат неопределено;
		конецесли;	   
		если ФЗ.Состояние=СостояниеФоновогоЗадания.ЗавершеноАварийно тогда
			Сообщить("Ошибка:"+ФЗ.ИнформацияОбОшибке.Описание);
			возврат неопределено;
		конецесли;										
	конецесли;	
	возврат 0;
конецфункции

Как видим, тут отслеживается состояние фонового задания (запущен, работает, завершен, завершен с ошибкой), но не отслеживается этап выполнения работ. Есть на самом деле два способа получения хода работы фонового задания:

  • перехват вывода функции Сообщить() на сервере, и парсинг данных из него. Например в фоновом задании можно с какой-то периодичностью выводить что-то вроде: Сообщить(«12.3%загружаю»);, а получив на клиенте эту запись показывать индикацию 12.3% и соответствующее пояснение.
  • Можно во время работы фонового задания ложить данные во временно хранилище, и читать их из клиента. НО! данный способ работает только в случай файловой БД. Циатата из справки 1С: «Данные, помещенные во временное хранилище в фоновом задании, не будут доступны из родительского сеанса до момента завершения фонового задания»

Посему остаётся таки отлавливать сообщения сервера по известному идентификатору фонового задания:

&НаСервере
Функция ПолучитьСообщенияФЗ(ФЗ, Состояние = Неопределено, УдалятьСообщения = Ложь) Экспорт
	Если Состояние = Неопределено Тогда
		Состояние = ФЗ.Состояние;
	КонецЕсли;
	МассивСообщений = Новый Массив;
	Сообщения = ФЗ.ПолучитьСообщенияПользователю(УдалятьСообщения);
	Если Сообщения <> Неопределено Тогда
		Для Каждого Сообщение Из Сообщения Цикл
			МассивСообщений.Добавить(Сообщение.Текст);
		КонецЦикла;
	КонецЕсли;
	Возврат МассивСообщений;
КонецФункции

А в самом фоновом здании, городить огород с выводом сообщений:

// На входе:
//	парам.ВременныйФайл
//	парам.Расширение
//	парам.ЛС_начало
//	парам.ЛС_лс
//	парам.ЛС_то
//	парам.ЛС_дата_установки_пу
//	парам.АдресВременногХранилища
Функция ЗагрузитьФайлыЛСРасширенно(парам) экспорт    
	Сообщить("0%читаю файл на сервере");
	ТабличныйДокументХар = Новый ТабличныйДокумент; 
	ТабличныйДокументХар.Прочитать(парам.ВременныйФайл,СпособЧтенияЗначенийТабличногоДокумента.Значение);
	стар=0;
	Для Каждого ОбластьТД ИЗ ТабличныйДокументХар.Области Цикл         		     
	    ОбластьФайла = ТабличныйДокументХар.ПолучитьОбласть(ОбластьТД.Имя);
	    КолВоСтрокФайла = ОбластьФайла.ПолучитьРазмерОбластиДанныхПоВертикали();
	    КолВоКолонокФайла = ОбластьФайла.ПолучитьРазмерОбластиДанныхПоГоризонтали();

		НачСтрока=парам.ЛС_начало;КонСтрока=0;
	    НачСтрока = ?(НачСтрока = 0, 2, НачСтрока);
	    КонСтрока = ?(КонСтрока = 0, КолвоСтрокФайла, КонСтрока);
					
		//перебираем все строки без первой строки            
		Для нСтрокаТФ = НачСтрока ПО КонСтрока Цикл      				
			если стар<>Окр(нСтрокаТФ*100/КонСтрока) тогда
				Сообщить(Строка(Окр(нСтрокаТФ*100/КонСтрока))+"%обрабатываю файл");
				стар=Окр(нСтрокаТФ*100/КонСтрока);
			конецесли;				
		конеццикла;		
	конеццикла						
конецфункции	

Посему модифицируем и обработчик ожидания, добавив парсинг сообщений сервера:

&НаКлиенте
Процедура ИндикаторВыполненияЗагрузкиФайловЛС() Экспорт  
	пр=КакДелаУФЗЗагрузкиФайловЛС();   	
	если пр=неопределено тогда  
		этаформа.ИндикаторЗагрузкиЛС=100;
		ОтключитьОбработчикОжидания("ИндикаторВыполненияЗагрузкиФайловЛС");
		Этаформа.Элементы.ПояснениеКЗагрузкеЛС.Заголовок="Файлы обработаны";
	иначе  
		если пр.Количество()>0 тогда
			этаформа.ИндикаторЗагрузкиЛС=пр[0];         		
			Этаформа.Элементы.ПояснениеКЗагрузкеЛС.Заголовок=пр[1];         		
		конецесли;
	конецесли;
КонецПроцедуры	 
&НаСервере
Функция КакДелаУФЗЗагрузкиФайловЛС()
	ФЗ = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(объект.ЛС_ФЗ);
	если ФЗ=Неопределено тогда		
		возврат неопределено;      
	иначе	                                                                                                    
		если ФЗ.Состояние=СостояниеФоновогоЗадания.Завершено тогда
			возврат неопределено;
		конецесли;	   
		если ФЗ.Состояние=СостояниеФоновогоЗадания.ЗавершеноАварийно тогда
			Сообщить("Ошибка:"+ФЗ.ИнформацияОбОшибке.Описание);
			возврат неопределено;
		конецесли;										
		ФСообщения=СК_ГР_ДлительныеОперации.ПолучитьСообщенияФЗ(ФЗ,,истина);
		Если ФСообщения.Количество() > 0 Тогда                  
			Для Каждого Сообщение Из ФСообщения Цикл				
				Если СтрНайти(Сообщение,"%")>0 тогда
					возврат СтрРазделить(Сообщение,"%");
				конецесли;	
			КонецЦикла;
		КонецЕсли;			
		
	конецесли;	
	возврат Новый Массив();                    
конецфункции

1 2 3 4 5 6 73