python

Форма № 16

  • четверг, 20 мая 2021 г. в 00:37:40
https://habr.com/ru/post/558190/
  • Python
  • HTML


Преподаватели, работающие в российских ВУЗах, периодически сталкиваются с необходимостью предоставить администрации список своих научных и учебно-методических работ. Например, для (пере)избрания на должность, присвоения звания и т. д. Формат представления информации, форма № 16, разработан невесть когда и до сих пор используется в бюрократических недрах Министерства Науки и Высшего Образования РФ. Мне стало лень заполнять эту форму вручную и я написал небольшой python сценарий, который генерирует нужную таблицу на основе информации, полученной из научной электронной библиотеки elibrary.ru. Возможно, кому-то это будет интересно, так что ниже приведено описание этой процедуры...

Итак, для начала нужно зайти на сайт elibrary.ru, авторизоваться, перейти в раздел «авторам» и далее в «мои публикации». Справа на экране есть колонка «инструменты», в которой есть кнопка «Вывести на печать список публикаций автора». Нажимаем на нее и в отдельном окне получаем таблицу со списком публикаций, которую сохраняем себе на диск в виде html-файла, который для простоты назовем index.html. Каждая публикация в этом списке выглядит примерно так:

Строка таблицы от elibrary.ru
Строка таблицы от elibrary.ru

Однако согласно приказу №268 Минобрнауки РФ (приложение №3 на с. 52) таблица зачем-то должна выглядеть иначе:

Строка таблицы в соответствии с формой №16
Строка таблицы в соответствии с формой №16

Сценарий для преобразования формата таблицы основан на использовании библиотеки BeautifulSoup, с которой я ознакомился весьма поверхностно и использовал первый раз в жизни. Вот что у меня вышло:

#!/usr/bin/env python3
from bs4 import BeautifulSoup
from random import randint
from re import findall 

YFrom, YTo = 2015, 2020                              # фильтр по годам публикаций

def NP(s): # функция для непредвзятого определения количества страниц в статье
  pages = s.split()[-1]
  if '-' in pages:
    P = pages.split('-')
    np =  1 + int(float(P[1])-float(P[0]))
  else:
    np = randint(5, 10)
  return '%d' % np # возвращает строку с числом
  
def Year(s, FROM, TO): # функция для отбора по году публикации
  Ys = findall(r'\s\d{4}\.', s)                 # форма записи года ' 2020.'
  if not Ys: Ys = findall(r'\s\d{4}', s)        # форма записи года ' 2020'
  if not Ys: return False        # если ничего не нашлось - выбрасываем                             
  for y in Ys: Y = int(float(y)) # в случае более чем одного совпадения берем последнее
  if Y<FROM or Y>TO: return False 
  else:              return True

with open('index.html', 'r') as fp: 
  soup = BeautifulSoup(fp, 'html.parser')              # загружаем исходный файл
soup.head.style.decompose()                            # убираем заголовок, css и т.д.
aname = soup.title.get_text().split('-')[1]            # имя автора
aname = f'СПИСОК опубликованных и приравненных к ним научных и учебно-методических работ {aname:s}\n'
soup.title.string = aname                              # новый заголовок
soup.find('span').string = aname                       # новый заголовок
soup.find('i').decompose()                             # убираем что-то лишнее
soup.find('table').decompose()                         # находим и удаляем первую таблицу
table = soup.find('table')                             # находим вторую таблицу
table['border'] = 1                                    # меняем оформление
table['width']  = '100%'                               # меняем ширину
N = 1                                                  # новый счетчик 
rows = table.find_all('tr')                            # ищем все строки в таблице
for i in range(len(rows)):                             # цикл для замены содержимого строк
  cols = rows[i].find_all('td')                        # ищем все столбцы
  if len(cols)==3 and cols[1].find('span'):            # проверка на соответствие стандартному шаблону
    content = cols[1].get_text()                       # читаем запись из второго столбца
    title   = cols[1].find('span').get_text()          # тут название
    authors = cols[1].find('i').get_text()             # тут авторы
    cites   = int(cols[2].get_text())                  # количество цитирований статьи
    content = content.replace(title, '')               # убираем навзвание, убираем список авторов:
    content = content.replace(authors, '')             # в content остаются только выходные данные
    thesis  = content.replace('В сборнике: ','')       # далее идет определение типа публикации
    abbook  = content.replace('В книге: ','')          # 
    if   thesis != content:                            #
      title += ' (тезисы)';      content = thesis      #
    elif abbook != content:                            #
      title += ' (тезисы)';      content = abbook      #
    else:                                              #
      if 'автореф'  in content: title+= ' (монография)'#
      elif 'диссер' in content: title+= ' (монография)'#
      else: title += ' (статья)'                       #
    authors = authors.split(', ')                      # получаем список авторов
    if cites<10 or not Year(content, YFrom, YTo):      # некоторые записи можно выбросить
      rows[i].decompose()                              #
    else:                                              # для других - определить кол-во соавторов 
      anumber = len(authors)
      if anumber<5: PS = ''
      else:         PS = f' и др., всего {anumber:d} чел.'
      authors = ', '.join(authors[0:5]) + PS

      cols[0].string = f'{N:3d}'                        # показания счетчика
      cols[1].string = title                            # название
      cols[2].string = "печ."                           # тип работы
      for info in [content, NP(content), authors]:      # еще три столбца 
        A = soup.new_tag('td');  A.string = info ; rows[i].append(A)
      N+= 1
  else:
    rows[i].decompose()

tr = soup.new_tag('tr') # вставляем заголовок таблицы
names = ['№ п\п', 'Наименование работы, её вид', 'Форма работы', 'Выходные данные', 'Объём в п.л. или с.', 'Соавторы']
for name in names:
  th = soup.new_tag('th') 
  th.string = name
  tr.append(th) 
table.insert(0, tr)
  
with open('table.html', 'w', encoding='utf-8') as fp: fp.write(str(soup))    

Для выполнения задачи нужно запустить сценарий в папке, в которой содержится файл index.html, в который мы сохранили таблицу с elibrary.ru. На выходе генерируется файл table.html, который можно легко загрузить в google docs, где и подвергнуть окончательным правкам типа изменения ширин столбцов, выбора шрифтов и т.д.