From 1409af98ce46d1dae7dcc4795ce2acda432603a9 Mon Sep 17 00:00:00 2001 From: alexxykv Date: Sun, 20 Jun 2021 21:19:02 +0500 Subject: [PATCH 1/6] Add Codeforces --- parsing/codeforces/codeforces.py | 100 +++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 parsing/codeforces/codeforces.py diff --git a/parsing/codeforces/codeforces.py b/parsing/codeforces/codeforces.py new file mode 100644 index 0000000..0153989 --- /dev/null +++ b/parsing/codeforces/codeforces.py @@ -0,0 +1,100 @@ +import requests +import json + + +class Codeforce: + """ + Class for accessing the Codeforce API. + """ + + API = 'https://codeforces.com/api/' + USER_AGENT = ('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) ' + + 'AppleWebKit/537.36 (KHTML, like Gecko) ' + + 'Chrome/39.0.2171.95 Safari/537.36') + + def __init__(self): + """Initialization""" + + self.session = requests.Session(); + self.session.headers = { + 'User-Agent': Codeforce.USER_AGENT, + } + + def _get(self, method_name: str, params: dict): + """ + GET request to codeforces API. + + `method_name`: Method API name + `params`: params + """ + + req = Codeforce.API + method_name + res = self.session.get(req, params=params) + + dict = json.loads(res.text) + status = dict['status'] + + if status == 'OK': + result = dict['result'] + return result + elif status == 'FAILED': + comment = dict['comment'] + raise HandleNotFound(comment) + + def user_info(self, handles: list): + """ + Returns information about one or more users. + + `handles`: A list of handles. You can transfer up to 10,000 handles. + """ + + method_name = 'user.info' + params = { + 'handles': ';'.join(handles), + } + + try: + res = self._get(method_name, params) + return res + except HandleNotFound: + return [] + + def user_blog_entries(self, handle: str): + """ + Returns a list of the specified user's blog entries. + + `handle`: Codeforces user handle. + """ + + method_name = 'user.blogEntries' + params = { + 'handle': handle, + } + + try: + res = self._get(method_name, params) + return res + except HandleNotFound: + return [] + + def user_rating(self, handle: str): + """ + Returns the rating history of the specified user. + + `handle`: Codeforces user handle. + """ + + method_name = 'user.rating' + params = { + 'handle': handle, + } + + try: + res = self._get(method_name, params) + return res + except HandleNotFound: + return [] + + +class HandleNotFound(Exception): + pass From 45f63fcc88ee55baaf349afd19e99d37d156431c Mon Sep 17 00:00:00 2001 From: alexxykv Date: Sun, 20 Jun 2021 22:14:37 +0500 Subject: [PATCH 2/6] Update Codeforces --- parsing/codeforces/codeforces.py | 46 +++++++++++++++++++++++++++++++- parsing/codeforces/utils.py | 9 +++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 parsing/codeforces/utils.py diff --git a/parsing/codeforces/codeforces.py b/parsing/codeforces/codeforces.py index 0153989..1f7f6cb 100644 --- a/parsing/codeforces/codeforces.py +++ b/parsing/codeforces/codeforces.py @@ -1,5 +1,6 @@ import requests import json +from utils import drop_tags class Codeforce: @@ -39,7 +40,11 @@ def _get(self, method_name: str, params: dict): return result elif status == 'FAILED': comment = dict['comment'] - raise HandleNotFound(comment) + + if comment.startswith('handles'): + raise HandleNotFound(comment) + elif comment.startswith('blogEntryId'): + raise BlogEntryIdNotFound(comment) def user_info(self, handles: list): """ @@ -95,6 +100,45 @@ def user_rating(self, handle: str): except HandleNotFound: return [] + def blog_entry_view(self, blog_entry_id): + """ + Returns a blog entry. + + `blog_entry_id`: Id of the blog entry. + """ + + method_name = 'blogEntry.view' + params = { + 'blogEntryId': blog_entry_id, + } + + try: + res = self._get(method_name, params) + + # drop tag

...

+ title = res['title'] + res['title'] = drop_tags(title) + # add url to result + res['url'] = self.get_blog_url(blog_entry_id) + + return res + except HandleNotFound: + return [] + + def get_blog_url(self, blog_entry_id): + """ + Returns a link to the blog. + + `blog_entry_id`: Id of the blog entry. + """ + + main_url = 'https://codeforces.com/blog/entry/' + blog_url = main_url + str(blog_entry_id) + + return blog_url class HandleNotFound(Exception): pass + +class BlogEntryIdNotFound(Exception): + pass diff --git a/parsing/codeforces/utils.py b/parsing/codeforces/utils.py new file mode 100644 index 0000000..0e95eca --- /dev/null +++ b/parsing/codeforces/utils.py @@ -0,0 +1,9 @@ +def drop_tags(title): + """ + Drop tag

...

+ + `title`: blog title + """ + title = title.replace('

', '') + title = title.replace('

', '') + return title From f5ed278a0acd5a9c760245093403e3342bdae6e0 Mon Sep 17 00:00:00 2001 From: alexxykv Date: Sun, 20 Jun 2021 22:14:37 +0500 Subject: [PATCH 3/6] Update Codeforces --- parsing/codeforces/codeforces.py | 65 +++++++++++++++++++++++++++++++- parsing/codeforces/utils.py | 9 +++++ 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 parsing/codeforces/utils.py diff --git a/parsing/codeforces/codeforces.py b/parsing/codeforces/codeforces.py index 0153989..4c8f666 100644 --- a/parsing/codeforces/codeforces.py +++ b/parsing/codeforces/codeforces.py @@ -1,5 +1,6 @@ import requests import json +from utils import drop_tags class Codeforce: @@ -39,7 +40,11 @@ def _get(self, method_name: str, params: dict): return result elif status == 'FAILED': comment = dict['comment'] - raise HandleNotFound(comment) + + if comment.startswith('handles'): + raise HandleNotFound(comment) + elif comment.startswith('blogEntryId'): + raise BlogEntryIdNotFound(comment) def user_info(self, handles: list): """ @@ -55,6 +60,12 @@ def user_info(self, handles: list): try: res = self._get(method_name, params) + + # add user profile url to res + for i in range(len(res)): + handle = res[i]['handle'] + res[i]['url'] = self.get_user_profile_url(handle) + return res except HandleNotFound: return [] @@ -95,6 +106,58 @@ def user_rating(self, handle: str): except HandleNotFound: return [] + def blog_entry_view(self, blog_entry_id): + """ + Returns a blog entry. + + `blog_entry_id`: Id of the blog entry. + """ + + method_name = 'blogEntry.view' + params = { + 'blogEntryId': blog_entry_id, + } + + try: + res = self._get(method_name, params) + + # drop tag

...

+ title = res['title'] + res['title'] = drop_tags(title) + # add blog url to res + res['url'] = self.get_blog_url(blog_entry_id) + + return res + except HandleNotFound: + return [] + + def get_blog_url(self, blog_entry_id): + """ + Returns a link to the blog. + + `blog_entry_id`: Id of the blog entry. + """ + + main_url = 'https://codeforces.com/blog/entry/' + blog_url = main_url + str(blog_entry_id) + + return blog_url + + def get_user_profile_url(self, handle): + """ + Returns a link to the user's profile. + + `handle`: Codeforces user handle. + """ + + main_url = 'https://codeforces.com/profile/' + user_profile_url = main_url + handle + + return user_profile_url + class HandleNotFound(Exception): pass + +class BlogEntryIdNotFound(Exception): + pass diff --git a/parsing/codeforces/utils.py b/parsing/codeforces/utils.py new file mode 100644 index 0000000..0e95eca --- /dev/null +++ b/parsing/codeforces/utils.py @@ -0,0 +1,9 @@ +def drop_tags(title): + """ + Drop tag

...

+ + `title`: blog title + """ + title = title.replace('

', '') + title = title.replace('

', '') + return title From 1e01b9dbfe505afc8679fe7de592c7b5a3edda77 Mon Sep 17 00:00:00 2001 From: Maxim Date: Sun, 20 Jun 2021 22:33:17 +0500 Subject: [PATCH 4/6] Add codeforce to result page --- main/templates/main/result.html | 18 +++- main/templates/main/results/codeforces.html | 16 ++++ main/views.py | 19 +++- parsing/codeforces/codeforces.py | 100 -------------------- 4 files changed, 48 insertions(+), 105 deletions(-) create mode 100644 main/templates/main/results/codeforces.html delete mode 100644 parsing/codeforces/codeforces.py diff --git a/main/templates/main/result.html b/main/templates/main/result.html index 6cd8dc3..393ebcc 100644 --- a/main/templates/main/result.html +++ b/main/templates/main/result.html @@ -57,7 +57,7 @@

Habr - {{ habr.ratio }} - {% if github != None %} + {% if github %}

Github - {{ github.ratio }} @@ -71,6 +71,22 @@

Github - {{ github.ratio }} {% endif %} + + {% if codeforces %} +

Codeforces + + + +

+
+
+ {% include 'main/results/codeforces.html' %} +
+
+ {% endif %} + + {% include 'main/scripts/show_hide.html' %} {% if sherlock %} diff --git a/main/templates/main/results/codeforces.html b/main/templates/main/results/codeforces.html new file mode 100644 index 0000000..217c741 --- /dev/null +++ b/main/templates/main/results/codeforces.html @@ -0,0 +1,16 @@ +
+
+

Rating: {{ codeforces.user.rating }}

+

Max rating: {{ codeforces.user.maxRating }}

+

Contribution: {{ codeforces.user.contribution }}

+

Rank: {{ codeforces.user.rank }}

+

Max rank: {{ codeforces.user.maxRank }}

+
+

Blogs:

+
    + {% for blog in codeforces.blogs %} +
  • {{ blog.title }} - {{ blog.rating }}
  • + {% endfor %} +
+
+
\ No newline at end of file diff --git a/main/views.py b/main/views.py index eae71a5..11e1400 100644 --- a/main/views.py +++ b/main/views.py @@ -7,10 +7,9 @@ from django.http import HttpResponse from sher import run from linkedin_api import Linkedin +from parsing.codeforces.codeforces import Codeforce import urllib.parse import pdfcrowd -from django.template import RequestContext -import json def index(request): @@ -61,7 +60,18 @@ def result(request): flag = True if flag: user_vk = Data.get_vk(info) - sherlock = run.search(user_vk[0]['screen_name']) if user_vk else None + sherlock = None + codeforces = None + if user_vk: + vk_nickname = user_vk[0]['screen_name'] + sherlock = run.search(vk_nickname) + code = Codeforce() + code_user = code.user_info([vk_nickname]) + if len(code_user) != 0: + codeforces = { + 'user': code_user[0], + 'blogs': code.user_blog_entries(vk_nickname) + } urls = CheckUrl(getting_sites(sites), sherlock).check() github = add_github(urls) habr = add_habr(urls) @@ -77,6 +87,7 @@ def result(request): 'habr': habr, 'github': github, 'linkedin': linkedin, + 'codeforces': codeforces, 'vk': user_vk[0] if user_vk else None, 'sherlock': sherlock, 'info': info, @@ -113,7 +124,7 @@ def add_linkedin(urls): def add_habr(urls): if 'habr.com' in urls: - # Mereics + # Metrics avg_values = { 'karma': 46.6, 'rating': 87.6, diff --git a/parsing/codeforces/codeforces.py b/parsing/codeforces/codeforces.py deleted file mode 100644 index 0153989..0000000 --- a/parsing/codeforces/codeforces.py +++ /dev/null @@ -1,100 +0,0 @@ -import requests -import json - - -class Codeforce: - """ - Class for accessing the Codeforce API. - """ - - API = 'https://codeforces.com/api/' - USER_AGENT = ('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) ' + - 'AppleWebKit/537.36 (KHTML, like Gecko) ' + - 'Chrome/39.0.2171.95 Safari/537.36') - - def __init__(self): - """Initialization""" - - self.session = requests.Session(); - self.session.headers = { - 'User-Agent': Codeforce.USER_AGENT, - } - - def _get(self, method_name: str, params: dict): - """ - GET request to codeforces API. - - `method_name`: Method API name - `params`: params - """ - - req = Codeforce.API + method_name - res = self.session.get(req, params=params) - - dict = json.loads(res.text) - status = dict['status'] - - if status == 'OK': - result = dict['result'] - return result - elif status == 'FAILED': - comment = dict['comment'] - raise HandleNotFound(comment) - - def user_info(self, handles: list): - """ - Returns information about one or more users. - - `handles`: A list of handles. You can transfer up to 10,000 handles. - """ - - method_name = 'user.info' - params = { - 'handles': ';'.join(handles), - } - - try: - res = self._get(method_name, params) - return res - except HandleNotFound: - return [] - - def user_blog_entries(self, handle: str): - """ - Returns a list of the specified user's blog entries. - - `handle`: Codeforces user handle. - """ - - method_name = 'user.blogEntries' - params = { - 'handle': handle, - } - - try: - res = self._get(method_name, params) - return res - except HandleNotFound: - return [] - - def user_rating(self, handle: str): - """ - Returns the rating history of the specified user. - - `handle`: Codeforces user handle. - """ - - method_name = 'user.rating' - params = { - 'handle': handle, - } - - try: - res = self._get(method_name, params) - return res - except HandleNotFound: - return [] - - -class HandleNotFound(Exception): - pass From 4a8e398835909c0253fa379bf9beb4ab40023c39 Mon Sep 17 00:00:00 2001 From: Maxim Date: Mon, 21 Jun 2021 00:44:25 +0500 Subject: [PATCH 5/6] Update codeforces & add codeforces to result page --- main/templates/main/result.html | 32 ++++++++++------ main/templates/main/results/codeforces.html | 3 +- main/templates/main/results/habr.html | 2 +- main/templates/main/results/linkedin.html | 1 + main/url_check.py | 8 +++- main/views.py | 42 ++++++++++++--------- parsing/codeforces/codeforces.py | 8 +++- 7 files changed, 63 insertions(+), 33 deletions(-) diff --git a/main/templates/main/result.html b/main/templates/main/result.html index 393ebcc..7509d4a 100644 --- a/main/templates/main/result.html +++ b/main/templates/main/result.html @@ -20,11 +20,19 @@
Дата рождения: {% if info.date_birth %}{{ inf Неизвестно{% endif %}
Город: {% if info.city %}{{ info.city }}{% else %} Неизвестно{% endif %}
-
Общая оценка: {{ info.ratio }}
+ {% if github %} +
Оценка по github: {{ github.ratio }}
+ {% endif %} + {% if habr %} +
Оценка по habr: {{ habr.ratio }}
+ {% endif %} + +
{% load static %} - photo + photo
@@ -32,15 +40,15 @@
Общая оценка: {{ info.ratio }}
{% if linkedin %} -

LinkedIn - -

-
-
- {% include 'main/results/linkedin.html' %} -
-
+

LinkedIn + +

+
+
+ {% include 'main/results/linkedin.html' %} +
+
{% endif %} @@ -74,7 +82,7 @@

Github - {{ github.ratio }} {% if codeforces %}

Codeforces + onclick="show('show_codeforces', 'hide_codeforces'); view('hidden_codeforces'); return false"> diff --git a/main/templates/main/results/codeforces.html b/main/templates/main/results/codeforces.html index 217c741..d62d315 100644 --- a/main/templates/main/results/codeforces.html +++ b/main/templates/main/results/codeforces.html @@ -1,5 +1,6 @@
+

Nickname: {{ codeforces.user.handle }}

Rating: {{ codeforces.user.rating }}

Max rating: {{ codeforces.user.maxRating }}

Contribution: {{ codeforces.user.contribution }}

@@ -9,7 +10,7 @@

Blogs:

    {% for blog in codeforces.blogs %} -
  • {{ blog.title }} - {{ blog.rating }}
  • +
  • {{ blog.title }} - {{ blog.rating }} (rating)
  • {% endfor %}
diff --git a/main/templates/main/results/habr.html b/main/templates/main/results/habr.html index 328401b..4ba13f9 100644 --- a/main/templates/main/results/habr.html +++ b/main/templates/main/results/habr.html @@ -30,7 +30,7 @@ {% include 'main/scripts/habr_avg_graph.html' %}
-

Место работы: {{ habr.main.work_name }}

+

Место работы: {% if habr.main.work_name %}{{ habr.main.work_name }}{% else %}Неизвестно{% endif %}

Среднее по статьям:

- Voitings: {{ habr.avgs.voitings }} {% if habr.analysis.voitings >= 0 %} diff --git a/main/templates/main/results/linkedin.html b/main/templates/main/results/linkedin.html index f54c10e..c8de9f9 100644 --- a/main/templates/main/results/linkedin.html +++ b/main/templates/main/results/linkedin.html @@ -1,5 +1,6 @@

+

Profile

{% if linkedin.headline %}

Обо мне: {{ linkedin.headline }}

{% endif %} diff --git a/main/url_check.py b/main/url_check.py index 94e00e9..960dbf6 100644 --- a/main/url_check.py +++ b/main/url_check.py @@ -10,7 +10,8 @@ def __init__(self, sites, sherlock): self.sherlock = sherlock def check(self): - pars_dict = {'github.com': self.github, 'habr.com': self.habr, 'www.linkedin.com': self.linkedin} + pars_dict = {'github.com': self.github, 'habr.com': self.habr, 'www.linkedin.com': self.linkedin, + 'codeforces.com': self.codeforces} for i in range(0, len(self.sites)): self.url = self.sites[i] domen = re.findall(r'//([^/]*\.[^/:]+)', self.url)[0] @@ -24,6 +25,11 @@ def check(self): self.result['habr.com'] = pars_dict['habr.com']() return self.result + def codeforces(self): + url = (self.url + '/').replace('//', '/') + nick = url.split('/')[-2] + return nick + def linkedin(self): url = (self.url + '/').replace('//', '/') nick = url.split('/')[-2] diff --git a/main/views.py b/main/views.py index 11e1400..1a36543 100644 --- a/main/views.py +++ b/main/views.py @@ -61,27 +61,14 @@ def result(request): if flag: user_vk = Data.get_vk(info) sherlock = None - codeforces = None if user_vk: vk_nickname = user_vk[0]['screen_name'] sherlock = run.search(vk_nickname) - code = Codeforce() - code_user = code.user_info([vk_nickname]) - if len(code_user) != 0: - codeforces = { - 'user': code_user[0], - 'blogs': code.user_blog_entries(vk_nickname) - } urls = CheckUrl(getting_sites(sites), sherlock).check() github = add_github(urls) habr = add_habr(urls) - linkedin = add_linkedin(urls) - info['ratio'] = 0 - if github: - info['ratio'] += github['ratio'] * 0.6 - if habr: - info['ratio'] += habr['ratio'] * 0.4 - info['ratio'] = round(info['ratio'], 2) + linkedin = add_linkedin(urls, info) + codeforces = add_codeforces(urls, user_vk) data = { 'form': form, 'habr': habr, @@ -107,15 +94,36 @@ def result(request): return redirect('/') -def add_linkedin(urls): +def add_codeforces(urls, user_vk): + if 'codeforces.com' in urls: + nick = urls['codeforces.com'] + elif user_vk: + nick = user_vk[0]['screen_name'] + else: + return None + code = Codeforce() + code_user = code.user_info([nick]) + if len(code_user) != 0: + return { + 'user': code_user[0], + 'blogs': code.user_blog_entries(nick) + } + + +def add_linkedin(urls, info): + api_link = Linkedin('+79065350750', '1qaz2wsx3edC') if 'www.linkedin.com' in urls: - api_link = Linkedin('+79065350750', '1qaz2wsx3edC') nickname = urls['www.linkedin.com'] + else: + linkedin = api_link.search_people(keyword_first_name=info['first_name'], keyword_last_name=info['last_name']) + nickname = linkedin[0]['public_id'] if linkedin else None + if nickname: profile = api_link.get_profile(nickname) contact = api_link.get_profile_contact_info(nickname) network = api_link.get_profile_network_info(nickname) linkedin = profile | contact | network linkedin['skills'] = api_link.get_profile_skills(nickname) + linkedin['url'] = 'https://www.linkedin.com/in/' + linkedin['profile_id'] # linkedin['updates'] = api_link.get_profile_updates(nickname, max_results=2) # print(json.dumps(linkedin['updates'])) return linkedin diff --git a/parsing/codeforces/codeforces.py b/parsing/codeforces/codeforces.py index 4c8f666..bc561d5 100644 --- a/parsing/codeforces/codeforces.py +++ b/parsing/codeforces/codeforces.py @@ -1,6 +1,6 @@ import requests import json -from utils import drop_tags +from .utils import drop_tags class Codeforce: @@ -84,6 +84,12 @@ def user_blog_entries(self, handle: str): try: res = self._get(method_name, params) + # drop tag

...

and add blog url + for i in range(len(res)): + title = res[i]['title'] + res[i]['title'] = drop_tags(title) + blog_entry_id = res[i]['id'] + res[i]['url'] = self.get_blog_url(blog_entry_id) return res except HandleNotFound: return [] From 88f90e94cd949b6f9c172b187ea8f796e47321ef Mon Sep 17 00:00:00 2001 From: Maxim Date: Mon, 21 Jun 2021 00:46:07 +0500 Subject: [PATCH 6/6] Alpha version 1.3 --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 92de1fe..93e2633 100644 --- a/README.md +++ b/README.md @@ -1 +1,12 @@ -# assessor +# Assessor + +## Описание +*Упрощение и ускорение поиска лучших кандидатов, их предварительной оценки по данным из резюме и открытых источников. +После получения резюме в формате hh.ru или ФИО, требуемых технических навыков и личных качеств, сервис выполняет поиск данных о кандидате в соц. сетях, открытых репозиториях и тд. Полученные данные анализируются для оценки навыков и экспертизы. А также проверки соответствия личных качеств и ценностей приемлемым в коллективе.* + +## Цель +*Скриннинг кандидата на вакансии ИТ по данным из резюме и из открытых источников (hh.ru, linkedin, habr, github, bitbucket.org, соц.сети и тд).* + +## Итоговый продукт +*Web-сервис/Telegram-бот +Оценка кандидата за разумное время (до 5 минут), предоставление развернутого отчета о навыках и личных качествах кандидата.* \ No newline at end of file