Compétences clés du programmeur Python

Dans notre époque dynamique, le programmeur doit se tenir au courant et apprendre constamment de nouvelles compétences afin de rester un spécialiste recherché.

Je programme en Python depuis environ deux ans, et maintenant le temps est venu d'aborder consciemment le développement de nouvelles compétences. Pour ce faire, j'ai décidé d'analyser les offres et de présenter les compétences requises sous forme de graphique. Je m'attendais à voir que les compétences formeront des clusters correspondant à différentes spécialités: développement backend, data science, etc. Mais qu'en est-il de la réalité? Tout d'abord.

Collecte de données


Vous devez d'abord décider de la source de données. J'ai considéré plusieurs options: Habr Career , Yandex Work , HeadHunter et autres. HeadHunter semblait le plus pratique, car ici, dans les postes vacants, il y a une liste de compétences clés et une API ouverte pratique .

Après avoir étudié l'API HeadHunter, j'ai décidé d'analyser d'abord la liste des identifiants de travail pour un mot clé donné (dans ce cas, «python»), puis d'analyser la liste des balises correspondantes pour chaque travail.

Lors de la recherche de postes vacants, les postes vacants sont renvoyés page par page, le nombre maximum de postes vacants par page est de 100. Au début, j'ai enregistré les résultats complets sous forme de liste de réponses de page.

Pour cela, le module requêtes a été utilisé. Dans le champ «user-agent», conformément à l'API, le nom du navigateur virtuel a été saisi pour que HH comprenne que le script y accède. Il a fait un léger délai entre les requêtes afin de ne pas surcharger le serveur.

ses = requests.Session()
ses.headers = {'HH-User-Agent': "Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20100101 Firefox/10.0"}

phrase_to_search = 'python'
url = f'https://api.hh.ru/vacancies?text={phrase_to_search}&per_page=100'
res = ses.get(url)

# getting a list of all pesponses
res_all = []
for p in range(res.json()['pages']):
    print(f'scraping page {p}')
    url_p = url + f'&page={p}'
    res = ses.get(url_p)
    res_all.append(res.json())
    time.sleep(0.2)

En conséquence, j'ai obtenu une liste de dictionnaires de réponses, où chaque dictionnaire correspondait à une page de résultats de recherche.

Il s'est avéré que l'API hh.ru limite le nombre maximal de postes vacants à deux mille, c'est-à-dire qu'avec 100 postes vacants par page, le nombre maximal de pages peut être de 20. Pour le mot clé Python, 20 pages de postes vacants ont été renvoyées, ce qui signifie que les postes vacants réels en Python sont plus susceptibles d'autant plus.

Pour obtenir une liste de balises, j'ai fait ce qui suit:
  • Itéré sur chaque page de résultats de recherche,
  • Itéré sur chaque travail sur la page et obtenu l'ID du travail,
  • demandé les détails de la vacance via l'API,
  • si au moins une balise a été spécifiée dans le poste vacant, la liste des balises a été ajoutée à la liste.

# parcing vacancies ids, getting vacancy page and scraping tags from each vacancy
tags_list = []
for page_res_json in res_all:
    for item in page_res_json['items']:
        vac_id = item['id']
        vac_res = ses.get(f'https://api.hh.ru/vacancies/{vac_id}')
        if len(vac_res.json()["key_skills"]) > 0:  # at least one skill present
            print(vac_id)
            tags = [v for v_dict in vac_res.json()["key_skills"] for _, v in v_dict.items()]
            print(' '.join(tags))
            tags_list.append(tags)
            print()
        time.sleep(0.1)

Les listes de balises ont été enregistrées sous forme de dictionnaire

res = {'phrase': phrase_to_search, 'items_number': len(tags_list), 'items': tags_list}
with open(f'./data/raw-tags_{phrase_to_search}.json', 'w') as fp:  # Serializing
    json.dump(res, fp)

Fait intéressant, sur les 2000 postes vacants consultés, seulement 1579 postes vacants avaient des étiquettes.

Formatage des données


Vous devez maintenant traiter les balises et les traduire dans un format pratique pour l'affichage sous forme de graphique, à savoir:
  • regrouper toutes les balises dans un seul registre, donc «apprentissage automatique», «apprentissage automatique» et «apprentissage automatique» signifient la même chose
  • calculer la valeur du nœud comme la fréquence d'occurrence de chaque balise,
  • calculer la valeur de la connexion comme la fréquence de réunion conjointe des étiquettes les unes avec les autres.

La réduction à un seul registre, le calcul de la fréquence d'occurrence de chaque étiquette, le filtrage par la taille du nœud ont été effectués comme suit.

tags_list['items'] = [[i.lower() for i in line] for line in tags_list['items']]

# counting words occurrences
flattened_list = [i for line in tags_list for i in line]
nodes_dict_all = {i: flattened_list.count(i) for i in set(flattened_list)}
nodes_dict = {k:v for k, v in nodes_dict_all.items() if v > del_nodes_count}

Occurrence par paire calculée comme suit. J'ai d'abord créé un dictionnaire dans lequel les clés étaient toutes les paires de balises possibles sous forme de tuple, et les valeurs étaient nulles. Ensuite, il a parcouru la liste des balises et augmenté les compteurs pour chaque paire rencontrée. Ensuite, j'ai supprimé tous ces éléments dont les valeurs étaient nulles.

# tags connection dict initialization
formatted_tags = {(tag1, tag2): 0 for tag1, tag2 in itertools.permutations(set(nodes_dict.keys()), 2)}

# count tags connection
for line in tags_list:
    for tag1, tag2 in itertools.permutations(line, 2):
        if (tag1, tag2) in formatted_tags.keys():
            formatted_tags[(tag1, tag2)] += 1

# filtering pairs with zero count
for k, v in formatted_tags.copy().items():
    if v == 0:
        del formatted_tags[k]

A la sortie, j'ai formé un dictionnaire

{
'phrase': phrase searched,
'items_number': number of vacancies parced, 
'items': {
 	"nodes": [
			{
			"id": tag name, 
		 	"group": group id, 
		 	"popularity": tag count
			},
		] 
	"links": [
			{
			"source": pair[0], 
			"target": pair[1], 
			"value": pair count
			},
		]
	}
}

nodes = []
links = []
for pair, count in formatted_tags.items():
    links.append({"source": pair[0], "target": pair[1], "value": count})

max_count = max(list(nodes_dict.values()))
count_step = max_count // 7
for node, count in nodes_dict.items():
    nodes.append({"id": node, "group": count // count_step, "popularity": count})

data_to_dump = in_json.copy()
data_to_dump['items'] = {"nodes": nodes, "links": links}

Visualisation Python


Pour visualiser le graphique, j'ai utilisé le module networkx. C'est ce qui s'est passé la première fois sans filtrer les nœuds.



Cette visualisation ressemble plus à une boule de fils emmêlés qu'à un graphique de compétences. Les connexions sont confuses et pénètrent le graphe si densément qu'il est impossible de distinguer les nœuds. De plus, il y a trop de nœuds sur le graphique, certains si petits qu'ils n'ont aucune signification statistique.

Par conséquent, j'ai filtré les plus petits nœuds de moins de 5 pouces et j'ai également créé des liens gris. Dans cette image, je n'ai pas encore apporté les mots à un seul registre, alors que j'essayais de supprimer le plus grand nœud Python afin de décharger la connexion.



C'est devenu beaucoup mieux. Maintenant, les nœuds sont séparés et les liens n'obstruent pas la visualisation. Il est devenu possible de voir les compétences de base, elles sont situées dans de grosses boules au centre du graphique, et de petits nœuds. Mais ce graphique a encore beaucoup à améliorer.

Visualisation JavaScript


Je continuerais probablement à choisir ce code si à ce moment je n'avais pas d'aide sous la forme d'un frère. Il a été activement impliqué dans le travail et a réalisé un bel affichage dynamique basé sur le module JavaScript D3 .

Cela s'est avéré comme ça.


La visualisation dynamique est disponible ici. Notez que les nœuds peuvent être extraits.

L'analyse des résultats


Comme nous pouvons le voir, le graphique s'est avéré être très entrelacé et des clusters clairement définis ne peuvent pas être détectés à première vue. Vous pouvez immédiatement remarquer plusieurs grands nœuds les plus demandés: linux, sql, git, postgresql et django. Il existe également des compétences de popularité moyenne et des compétences rarement rencontrées.

De plus, vous pouvez prêter attention au fait que les compétences forment toujours des grappes par profession, situées sur les côtés opposés du centre:

  • en bas à gauche - analyse des données,
  • en bas se trouvent les bases de données,
  • en bas à droite - développement frontal,
  • à droite, on teste,
  • en haut à droite - développement web,
  • en haut à gauche - apprentissage automatique.

Cette description des clusters est basée sur mes connaissances et peut contenir des erreurs, mais l'idée elle-même, j'espère, est claire.

Sur la base des résultats obtenus, les conclusions suivantes peuvent être tirées:
  • vous devez maîtriser des compétences qui correspondent à de grands nœuds, elles seront toujours utiles,
  • vous devez maîtriser les compétences du cluster qui correspond à vos intérêts.

J'espère que cela vous a plu et cette analyse vous sera utile.

Vous pouvez jeter un œil au code ou participer à son développement en utilisant les liens: projet GitHub , ordinateur portable observable avec visualisation

Succès dans la maîtrise de nouveaux horizons!

All Articles