Форум программистов, компьютерный форум, киберфорум
Python для начинающих
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.55/11: Рейтинг темы: голосов - 11, средняя оценка - 4.55
0 / 0 / 0
Регистрация: 05.05.2020
Сообщений: 2

Код-ревью

05.05.2020, 11:45. Показов 2215. Ответов 3
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Добрый день! Я написал небольшой парсер вакансий для hh.ru. Буду благодарен, если поможете прояснить несколько вопросов по коду:

1.Переменная last_link передается из одной функции в другую. Вскоре мне может понадобиться передавать еще одну переменную (дату вакансии) таким же образом. Возможно, лучше сделать что-то вроде глобальных переменных?
2.Стоит ли использовать ООП в этом проекте?
3.Правильно ли хранить логин и пароль прямо в коде? Если нет, то как можно поступить?
4.Я совсем новичок, поэтому в целом буду рад советам по структуре/оформлению кода
Кликните здесь для просмотра всего текста

Python
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
185
186
187
188
189
190
191
192
193
import io
import sys
from copy import deepcopy
import requests
from bs4 import BeautifulSoup
import sqlite3
import time
 
#ссылки, которые используются:
#https://kazan.hh.ru/login
#https://kazan.hh.ru/search/vacancy?order_by=publication_time&schedule=remote&clusters=true&enable_snippets=true&search_period=7&page=0&items_on_page=100&no_magic=true
CONFIG = {
    "kazan.hh.ru": {
        "login_form_url": "https://kazan.hh.ru/login",
        "url": "https://kazan.hh.ru/account/login?backurl=%2F",
        "vacancies_url": "https://kazan.hh.ru/search/vacancy",
        "login_data": {
            "backUrl": "https://kazan.hh.ru/",
            "failUrl": "/account/login?backurl=%2F",
            # "username": "login@gmail.com",
            # "password": "password",
        },
        "search_data": {
            "order_by": "publication_time",
            "schedule": "remote",
            "enable_snippets": "true",
            "clusters": "true",
            "search_period": "7",
            "page": "0",
            "items_on_page": "100",
            "no_magic": "true"
        },
    }
}
 
DB_PATH = 'test.db'
 
DISALLOWED_WORDS = 'ведущий, журналист, автор, копирайтер, инженер, водитель, директор, писатель' \
                   'телефон, call, саll, сall'
 
 
DISALLOWED_COMPANIES = 'Макдоналдс'
 
s = requests.Session()
s.headers.update({'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64)'
                  'AppleWebKit/537.36 (KHTML, like Gecko)'
                  'Chrome/72.0.3626.121 Safari/537.36'})
 
 
def write_to_db(vacancies, path=DB_PATH):
    conn = sqlite3.connect(path)
    c = conn.cursor()
    c.execute('''CREATE TABLE IF NOT EXISTS attempts (
                     id INTEGER PRIMARY KEY,
                     timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
                 )''')
    c.execute('''CREATE TABLE IF NOT EXISTS vacancies (
                     id INTEGER PRIMARY KEY,
                     title TEXT,
                     link TEXT,
                     company TEXT,
                     city TEXT,
                     attempt_id INTEGER,
                     FOREIGN KEY(attempt_id) REFERENCES
                         attempts (id)
                 )''')
 
    c.execute('''INSERT INTO attempts DEFAULT VALUES''')
    attempt_id = c.lastrowid
    for v in vacancies:
        c.execute("INSERT INTO vacancies (title,link,company,city,attempt_id) VALUES (?,?,?,?,?)", [v["title"], v["link"], v["company"], v["city"], attempt_id])
 
    conn.commit()
    conn.close()
 
 
def get_response(url, session, data=None):
    if data is not None:
        return session.get(url, data=data)
    return session.get(url)
 
 
def get_xsrf(url, session):
    html = get_response(url, session).text
    bs = BeautifulSoup(html, "lxml")
    return bs.find("input", {"name": "_xsrf"})["value"]
 
 
def login(session, config):
    login_data = deepcopy(config["login_data"])
    login_data["_xsrf"] = get_xsrf(config["login_form_url"], session)
    r = session.post(config["url"], data=login_data)
 
 
def get_last_link(path=DB_PATH):
    try:
        conn = sqlite3.connect(path)
        c = conn.cursor()
        c.execute('''SELECT * FROM attempts ORDER BY timestamp DESC LIMIT 1''')
        last_attempt_id = c.fetchone()[0]
        c.execute('''SELECT * FROM vacancies WHERE attempt_id = ?''', (last_attempt_id,))
        result = c.fetchone()[2]
        conn.close()
    except:
        result = ''
    return result
 
 
def is_allowed(text, disallowed_words):
    for dw in disallowed_words.split(','):
        if dw.strip().lower() in text.lower():
            return False
    return True
 
 
def get_data(html, last_link):
    last_vacancy_reached = False
    bs = BeautifulSoup(html, "lxml")
    items = bs.find("div", class_="vacancy-serp") \
              .find_all("div", class_="vacancy-serp-item")
    result = []
    for item in items:
        if len(item["class"]) != 1:
            continue
        link = item.find("span", class_="resume-search-item__name").find("a")
        title = link.text.strip()
        if not is_allowed(title, DISALLOWED_WORDS):
            continue
 
        link = link["href"]
        if link == last_link:
            last_vacancy_reached = True
            break
        divs = item.find_all("div", class_="vacancy-serp-item__meta-info")
        company = divs[0].find("a").text.strip()
 
        last_title = title
        last_company = company
 
        if not is_allowed(company, DISALLOWED_COMPANIES):
            continue
        city = divs[1].find("span").text.strip()
 
        data = {"title": title,
                "link": link,
                "company": company,
                "city": city}
        result.append(data)
    return result, last_vacancy_reached
 
 
def get_vacancies(last_link, config):
    result = []
    search_data = deepcopy(config["search_data"])
    while True:
        vacancies_html = get_response(config["vacancies_url"], s, search_data).text
        data_list, last_vacancy_reached = get_data(vacancies_html, last_link)
        result = result + data_list
        if last_vacancy_reached or int(search_data["page"]) >= 19:
            break
        search_data["page"] = str(int(search_data["page"]) + 1)
    return result
 
 
def remove_duplicates(vacancies):
    titles = [t['title'] for t in vacancies]
    companies = [c['company'] for c in vacancies]
    tc = list(zip(titles, companies))
    inds = []
    seen = set()
    for i, ele in enumerate(tc):
        if ele not in seen:
            inds.append(i)
        seen.add(ele)
 
    result = [vacancies[i] for i in inds]
    return result
 
def main():
    #залогиниться на hh.ru
    login(s, config=CONFIG["kazan.hh.ru"])
    #last_link - это ссылка на последнюю полученную вкансию из предыдущего сбора вакансий
    last_link = get_last_link()
    #собрать вакансии. Передаю last_link, чтобы собирать каждую новую вакансию, пока ее ссылка не будет
    #равна last_link
    vacancies = get_vacancies(last_link, config=CONFIG["kazan.hh.ru"])
    if len(vacancies) > 0:
        vacancies = remove_duplicates(vacancies)
        write_to_db(vacancies)
 
 
if __name__ == '__main__':
    main()
0
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
05.05.2020, 11:45
Ответы с готовыми решениями:

Код-ревью. Игра блек джек
Здравствуйте. Изучаю Python как первый язык программирования. Задание: Написать игру Блэк Джек. Попинайте что реализовано не...

Ревью "Крестики-нолики"
Добрый день, просьба глянуть код, буду рад предложениям/замечаниям :) Так же буду рад если скажите, что все "Ок" :jokingly: ...

Код-ревью
Всем здрасте, можете посмотреть на мою программку и рассказать как можно сделать лучше и легче? import random leftz =...

3
Просто Лис
Эксперт Python
 Аватар для Рыжий Лис
5973 / 3735 / 1099
Регистрация: 17.05.2012
Сообщений: 10,791
Записей в блоге: 9
05.05.2020, 12:03
1) конфиг лучше вынести в отдельный файл:
Python
1
from settings import CONFIG
2) Возьмите ORM и не мучайтесь с sql.

Дёргать
SQL
1
CREATE TABLE IF NOT EXISTS
При каждом insert'е - не лучшая идея. Выполняйте это один раз при старте приложения.

Так же соединение с базой лучше держать открытым всегда. И когда нужно - делать коммиты.

3)
Python
1
2
3
4
def get_response(url, session, data=None):
    if data is not None:
        return session.get(url, data=data)
    return session.get(url)
Ты не поверишь:
Python
1
return s.get(url, data=data)
4)
Python
1
disallowed_words.split(','):
Лучше сразу генерить данные в нужном формате:
Python
1
2
DISALLOWED_WORDS = 'ведущий, журналист, автор, копирайтер, инженер, водитель, директор'
DISALLOWED_WORDS = re.findall(r'\w+', DISALLOWED_WORDS)
Ответы на вопросы:
1) можно сделать и глобальную переменную. Я бы создал класс, пусть и экземпляр класса будет всего один.

2) ООП скорей всего не стоит использовать. Только для ORM (базы данных).

3) нормально. Как вариант, можно хранить в отдельном конфиге или передавать через переменные окружения.
1
0 / 0 / 0
Регистрация: 05.05.2020
Сообщений: 2
08.05.2020, 13:38  [ТС]
Рыжий Лис, подскажите еще, пожалуйста, как лучше поступить с экземпляром Session? Меня смущает, что его приходится передавать в функции login, get_xsrf, get_response (который к тому же находится внутри get_vacancies).
0
Просто Лис
Эксперт Python
 Аватар для Рыжий Лис
5973 / 3735 / 1099
Регистрация: 17.05.2012
Сообщений: 10,791
Записей в блоге: 9
08.05.2020, 13:47
Сделать переменную глобальной - делов-то. Но это если вы не планируете парсить в несколько потоков - тогда потребуются несколько соединений до сервера.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
08.05.2020, 13:47
Помогаю со студенческими работами здесь

Код ревью python кода
Всем привет, хотелось бы, чтобы кто-нибудь посмотрел на мой код(и комментарии к коммитам) и покритиковал. Ссылка на репозиторий:...

Прошу сделать код ревью с конструктивной критикой
Добрый день! Прошу покритиковать код. https://www43.zippyshare.com/v/OoX5wDpx/file.html

Код-ревью
Прошу прокомментировать код на наличия каких-то явных ошибок и возможно подсказать какие варианты оптимизации кода create or replace...

Код ревью
В общем препод дал open source проект и дал задание сделать ревью кода какого - то класса... Вот только с java и ООП нашей группе предстоит...

Код ревью
Добрый день. Сделал ajax в ajax и вижу дубляж кода, но не знаю как от него избавиться. Правила форума 5. Запреты и ограничения. ...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
4
Ответ Создать тему
Новые блоги и статьи
Перемещение выделенных строк ТЧ из одного документа в другой
Maks 30.03.2026
Реализация из решения ниже выполнена на примере нетипового документа "ВыдачаОборудованияНаСпецтехнику" с единственной табличной частью "ОборудованиеИКомплектующие" разработанного в конфигурации КА2. . . .
Functional First Web Framework Suave
DevAlt 30.03.2026
Sauve. IO Апнулись до NET10. Из зависимостей один пакет, работает одинаково хорошо как в режиме проекта так и в интерактивном режиме. из сложностей - чисто функциональный подход. Решил. . .
Автоматическое создание документа при проведении другого документа
Maks 29.03.2026
Реализация из решения ниже выполнена на нетиповых документах, разработанных в конфигурации КА2. Есть нетиповой документ "ЗаявкаНаРемонтСпецтехники" и нетиповой документ "ПланированиеСпецтехники". В. . .
Настройка движения справочника по регистру сведений
Maks 29.03.2026
Решение ниже реализовано на примере нетипового справочника "ТарифыМобильнойСвязи" разработанного в конфигурации КА2, с целью учета корпоративной мобильной связи в коммерческом предприятии. . . .
Автозаполнение реквизита при выборе элемента справочника
Maks 27.03.2026
Программный код из решения ниже на примере нетипового документа "ЗаявкаНаРемонтСпецтехники" разработанного в конфигурации КА2. При выборе "Спецтехники" (Тип Справочник. Спецтехника), заполняется. . .
Сумматор с применением элементов трёх состояний.
Hrethgir 26.03.2026
Тут. https:/ / fips. ru/ EGD/ ab3c85c8-836d-4866-871b-c2f0c5d77fbc Первый документ красиво выглядит, но без схемы. Это конечно не даёт никаких плюсов автору, но тем не менее. . . всё может быть. . .
Автозаполнение реквизитов при создании документа
Maks 26.03.2026
Программный код из решения ниже размещается в модуле объекта документа, в процедуре "ПриСозданииНаСервере". Алгоритм проверки заполнения реализован для исключения перезаписи значения реквизита,. . .
Команды формы и диалоговое окно
Maks 26.03.2026
1. Команда формы "ЗаполнитьЗапчасти". Программный код из решения ниже на примере нетипового документа "ЗаявкаНаРемонтСпецтехники" разработанного в конфигурации КА2. В качестве источника данных. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru