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

Код-ревью

05.05.2020, 11:45. Показов 2162. Ответов 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
 Аватар для Рыжий Лис
5972 / 3734 / 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
 Аватар для Рыжий Лис
5972 / 3734 / 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
Ответ Создать тему
Новые блоги и статьи
изучаю kubernetes
lagorue 13.01.2026
А пригодятся-ли мне знания kubernetes в России?
сукцессия микоризы: основная теория в виде двух уравнений.
anaschu 11.01.2026
https:/ / rutube. ru/ video/ 7a537f578d808e67a3c6fd818a44a5c4/
WordPad для Windows 11
Jel 10.01.2026
WordPad для Windows 11 — это приложение, которое восстанавливает классический текстовый редактор WordPad в операционной системе Windows 11. После того как Microsoft исключила WordPad из. . .
Classic Notepad for Windows 11
Jel 10.01.2026
Old Classic Notepad for Windows 11 Приложение для Windows 11, позволяющее пользователям вернуть классическую версию текстового редактора «Блокнот» из Windows 10. Программа предоставляет более. . .
Почему дизайн решает?
Neotwalker 09.01.2026
В современном мире, где конкуренция за внимание потребителя достигла пика, дизайн становится мощным инструментом для успеха бренда. Это не просто красивый внешний вид продукта или сайта — это. . .
Модель микоризы: классовый агентный подход 3
anaschu 06.01.2026
aa0a7f55b50dd51c5ec569d2d10c54f6/ O1rJuneU_ls https:/ / vkvideo. ru/ video-115721503_456239114
Owen Logic: О недопустимости использования связки «аналоговый ПИД» + RegKZR
ФедосеевПавел 06.01.2026
Owen Logic: О недопустимости использования связки «аналоговый ПИД» + RegKZR ВВЕДЕНИЕ Введу сокращения: аналоговый ПИД — ПИД регулятор с управляющим выходом в виде числа в диапазоне от 0% до. . .
Модель микоризы: классовый агентный подход 2
anaschu 06.01.2026
репозиторий https:/ / github. com/ shumilovas/ fungi ветка по-частям. коммит Create переделка под биомассу. txt вход sc, но sm считается внутри мицелия. кстати, обьем тоже должен там считаться. . . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru