Principais habilidades do programador Python

Em nosso tempo dinâmico, o programador precisa manter-se a par e aprender constantemente novas habilidades para permanecer um especialista procurado.

Faço programação em Python há cerca de dois anos e agora chegou a hora de abordar conscientemente o desenvolvimento de novas habilidades. Para isso, decidi analisar as vagas e apresentar as habilidades necessárias na forma de gráfico. Eu esperava ver que as habilidades formariam grupos correspondentes a diferentes especialidades: desenvolvimento de back-end, ciência de dados etc. Mas e a realidade? Primeiras coisas primeiro.

Coleção de dados


Primeiro você tinha que decidir sobre a fonte de dados. Eu considerei várias opções: Habr Career , Yandex Work , HeadHunter e outras. O HeadHunter parecia o mais conveniente, porque aqui nas vagas há uma lista de habilidades-chave e uma API aberta conveniente .

Tendo estudado a API do HeadHunter, decidi analisar primeiro a lista de IDs de trabalhos para uma determinada palavra-chave (neste caso, “python”) e, em seguida, analisar a lista de tags correspondentes para cada trabalho.

Ao procurar vagas, as vagas são retornadas página por página, o número máximo de vagas por página é 100. No início, salvei os resultados completos como uma lista de respostas da página.

Para isso, foi utilizado o módulo de solicitações. No campo "user-agent", de acordo com a API, o nome do navegador virtual foi inserido para que HH entendesse que o script estava acessando. Ele fez um pequeno atraso entre as solicitações para não sobrecarregar o servidor.

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)

Como resultado, recebi uma lista de dicionários de respostas, em que cada dicionário correspondia a uma página de resultados de pesquisa.

Como se viu, a API hh.ru limita o número máximo de vagas a dois mil, ou seja, com 100 vagas por página, o número máximo de páginas pode ser 20. Para a palavra-chave Python, foram retornadas 20 páginas de vaga, o que significa que as vagas reais em Python são mais prováveis tudo o mais.

Para obter uma lista de tags, fiz o seguinte:
  • Iterado sobre cada página dos resultados de pesquisa,
  • Iterou cada trabalho na página e obteve o ID do trabalho,
  • solicitou detalhes da vaga por meio da API,
  • se pelo menos uma tag foi especificada na vaga, a lista de tags foi adicionada à lista.

# 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)

As listas de tags foram salvas como um dicionário

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)

Curiosamente, das 2000 vagas visualizadas, apenas 1579 vagas possuíam tags.

Formatação de dados


Agora você precisa processar as tags e traduzi-las para um formato conveniente para exibição em gráfico, a saber:
  • traga todas as tags para um único registro, para que “aprendizado de máquina”, “aprendizado de máquina” e “aprendizado de máquina” signifiquem a mesma coisa
  • calcular o valor do nó como a frequência de ocorrência de cada tag,
  • calcule o valor da conexão como a frequência da reunião conjunta de tags.

Reduzindo para um único registro, calculando a frequência de ocorrência de cada tag, a filtragem pelo tamanho do nó foi realizada da seguinte maneira.

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}

Ocorrência em pares calculada da seguinte forma. Primeiro, criei um dicionário no qual as chaves eram todos possíveis pares de tags na forma de tupla e os valores eram zero. Depois, percorreu a lista de tags e aumentou os contadores para cada par encontrado. Então eu apaguei todos os elementos cujos valores eram zero.

# 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]

Na saída, formei um dicionário do formulário

{
'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}

Visualização Python


Para visualizar o gráfico, usei o módulo networkx. Foi o que aconteceu pela primeira vez sem filtrar os nós.



Essa visualização é mais uma bola de fios emaranhados do que um gráfico de habilidades. As conexões são confusas e penetram no gráfico com tanta densidade que é impossível distinguir nós. Além disso, existem muitos nós no gráfico, alguns tão pequenos que eles não têm significância estatística.

Portanto, filtramos os menores nós com menos de 5 e também fiz links em cinza. Nesta figura, eu ainda não trouxe as palavras para um único registro, enquanto tentava excluir o maior nó Python para descarregar a conexão.



Tornou-se muito melhor. Agora os nós estão separados e os links não obstruem a visualização. Tornou-se possível ver as habilidades básicas, elas estão localizadas em bolas grandes no centro do gráfico e em nós pequenos. Mas este gráfico ainda tem muito a melhorar.

Visualização JavaScript


Eu provavelmente continuaria escolhendo esse código se naquele momento não tivesse ajuda na forma de um irmão. Ele se envolveu ativamente no trabalho e fez uma bela exibição dinâmica baseada no módulo JavaScript D3 .

Acabou assim.


A visualização dinâmica está disponível aqui. Observe que os nós podem ser puxados.

Análise de Resultados


Como podemos ver, o gráfico mostrou-se muito interligado, e clusters claramente definidos não podem ser detectados à primeira vista. Você pode notar imediatamente vários nós grandes que são os mais procurados: linux, sql, git, postgresql e django. Também existem habilidades de popularidade média e habilidades raramente encontradas.

Além disso, você pode prestar atenção ao fato de que as habilidades ainda formam grupos por profissão, localizadas em lados opostos do centro:

  • canto inferior esquerdo - análise de dados,
  • no fundo estão os bancos de dados,
  • canto inferior direito - desenvolvimento front-end,
  • à direita está testando,
  • canto superior direito - desenvolvimento web,
  • canto superior esquerdo - aprendizado de máquina.

Essa descrição dos clusters é baseada no meu conhecimento e pode conter erros, mas espero que a ideia em si seja clara.

Com base nos resultados obtidos, as seguintes conclusões podem ser tiradas:
  • você precisa dominar habilidades que correspondem a nós grandes, elas sempre serão úteis,
  • você precisa dominar as habilidades do cluster que atendem aos seus interesses.

Espero que tenham gostado e esta análise seja útil para você.

Você pode dar uma olhada no código ou participar de seu desenvolvimento usando os links: projeto GitHub , laptop observável com visualização

Sucesso em dominar novos horizontes!

All Articles