Парсинг веб-сайта поиска работы 'JobsDB' с использованием beautifulsoup (прохождение классной работы)
Table Of Content
- Цель: Спарсить локальный сайт поиска работы [JobsDB](https://hk.jobsdb.com/hk)
- Шаг 1. Сначала изучите структуру.
- **Шаг 2. Извлечение всех URL-адресов вакансий**
- **Шаг 3. Извлечение информации из каждого поста**
- Шаг 4. Запись в CSV файл (необязательно)
- **Шаг 5. Извлечение требуемых навыков для работы**
- **Шаг 6. Подсчет наиболее востребованных навыков и экспорт в csv**
- Шаг 7. Результат и ограничения
Я сейчас учусь в буткемпе по науке о данных, и нам требуется применить навыки парсинга веб-сайтов к локальному сайту поиска работы. Эта статья служит сводкой для моего обучения и справочником для начинающих. Мой подход и код предназначены только для ознакомления, наверняка есть много других лучших способов. Я объясню идею, полный код я размещу в конце этой статьи, а позже на GitHub. Удачи!
Цель: Спарсить локальный сайт поиска работы JobsDB
Мысленный штурм и трудности: Я хочу получить следующую информацию: название вакансии, компания, требуемые навыки, опыт работы и т.д., а затем узнать, какие навыки являются наиболее важными для работы аналитиком данных в Гонконге. Для начала я прочитал несколько статей, но их сайты поиска работы отличаются от JobsDB. Во-первых, JobsDB не показывает подробной информации, пока вы не щелкнете на объявление о вакансии вручную. Лучший способ решить эту проблему, возможно, использовать Selenium / scrapy, но я не знаком с ними. Во-вторых, информация скрыта в теге скрипта / xpath, что делает извлечение данных более сложным.
Шаг 1. Сначала изучите структуру.
Вы можете видеть, что каждая страница отображает максимум 30 результатов. Когда вы загружаете HTML с помощью запроса и проверяете код этой страницы, у вас не будет доступа к отдельным результатам. Однако в теге <script>
вы можете найти все URL-ссылки на отдельные записи этой страницы.
Таким образом, мой подход заключается в следующем: получить все отдельные URL-адреса записей с КАЖДОЙ страницы. Затем парсить каждую вакансию по отдельности, используя цикл. Сначала нам нужно узнать, сколько раз мы должны выполнить цикл, чтобы получить все URL-адреса записей со всех страниц.
Шаг 2. Извлечение всех URL-адресов вакансий
Сначала нам нужно знать, сколько раз нам нужно выполнить цикл, чтобы получить все URL-адреса вакансий со всех страниц. Мы можем получить общее количество вакансий с помощью следующего кода:
total_num_jobs = re.findall('\d+', soup.find('meta', property="og:description")['content'])
Поскольку на каждой странице отображается максимум 30 результатов, нам нужно разделить количество вакансий на 30, чтобы определить, сколько страниц нам нужно просмотреть.
Жизнь не так проста. Так называемые URL-адреса вакансий, которые мы собираем, полны примесей. Нам нужно дальше обработать их с помощью регулярных выражений. Нам нужна только последняя часть URL-адреса. (Мой одноклассник может использовать подходящий тег для нахождения "чистого" URL-адреса вакансии, но это его метод, и я не должен копировать его. Работа с структурой HTML и тегами может облегчить веб-парсинг.)
script_str = soup.find_all("script")[1].get_text()[28:]
pattern = re.compile(r'(https\W\Wu002F\Wu002Fhk\Wjobsdb\Wcom\Wu002Fhk\Wu002Fen\Wu002Fjob\Wu002F)([a-zA-Z0-9]+\W)*')
job_url_raw = []
for i in pattern.finditer(script_str):
job_url_raw.append(i[0])
for i in job_url_raw:
a = re.sub("u002F",'',i)
b = re.sub('"','',a)
c = b.split('\\')[-1]
job_id.append(c)
Шаг 3. Извлечение информации из каждого поста
job_title = soup.find(class_='general-pos ad-y-auto-txt2').text.strip()
company = soup.find(class_='jobad-header-company').text.lstrip()
yr_exp = soup.find('b', class_='primary-meta-exp').text.strip()
yr_exp = yr_exp[0]
level = soup.find('b', class_='primary-meta-lv').text.strip()
Очень жаль, что я потратил столько времени, пытаясь извлечь точный текст «ответственности» и «требований» из объявления о вакансии. Однако, когда я попытался применить функцию цикла для всех объявлений о вакансиях, я обнаружил, что JobsDB использует разные методы тегов и атрибутов для хранения этой информации. Поэтому я не могу придумать краткий универсальный способ извлечения этих двух частей. В конце концов, я могу использовать <span>
, который на самом деле возвращает так много ненужных данных.
role = [i.text.lower() for i in soup.find_all('span')]
Шаг 4. Запись в CSV файл (необязательно)
with open('jobsDBtest_csv_result.csv', 'a') as f:
csv_writer = csv.writer(f)
csv_writer.writerow([i, job_title, company, yr_exp, level, required_skill])
На самом деле это необязательно, потому что мы можем использовать pandas для обработки этой информации. У меня возникли проблемы с кодировкой в Excel и не все значения отображаются, но кому это важно.
Шаг 5. Извлечение требуемых навыков для работы
Мой подход глупый. Поскольку я не могу точно извлечь обязанности и требования к работе, я не собираюсь использовать сложные методы, такие как создание векторного представления слов для выполнения этой задачи. Я создам список, содержащий все общие ключевые слова для работы аналитика данных/специалиста по обработке данных, а затем использую регулярные выражения для перебора извлеченной информации о работе и проверки, содержит ли она общие ключевые слова для работы. Если ключевое слово присутствует, мы захватим это ключевое слово и добавим его в список, чтобы в конце увидеть, какие навыки наиболее требуются работодателями в Гонконге.
required_skill=[]
for i in skill_set:
result = re.search(i, role2)
if result:
required_skill.append(i)
skill_list.append(i)
else:
pass
Шаг 6. Подсчет наиболее востребованных навыков и экспорт в csv
frequency={}
for item in skill_list:
frequency[item]=frequency.get(item,0)+1
import pandas as pd
skill_count=pd.Series(frequency)
skill_count.sort_values(ascending=False,inplace=True)
skill_countskill_count.to_csv('data_skills_count.csv')
Шаг 7. Результат и ограничения
Как видно из результатов, наиболее часто встречающимися навыками являются "bi" (сокращение от Business Intelligence) и "r". Я бы сказал, что их следует удалить из-за ограничений моего скрипта. Я использую регулярные выражения для поиска требуемых навыков в объявлениях о вакансиях, но "r" и "bi" являются слишком общими. Например, если исходная фраза - "are", использование "r" для поиска все равно вернет положительный результат, так же и с "bi".
На самом деле, Excel может быть потенциально ложным положительным результатом, потому что они всегда говорят "отличные навыки в Excel", но по моему опыту Excel важен в работе, связанной с данными, в Гонконге.
Я немного разочарован, потому что мой интерес больше связан с разработкой искусственного интеллекта с использованием глубокого обучения с помощью Tensorflow, Keras и PyTorch. Всего 739 объявлений о вакансиях, связанных с "data" на JobsDB (на данный момент), но глубокое обучение, Keras, Tensorflow, PyTorch - это САМЫЕ редко требуемые навыки....
Дальнейшие идеи для работы: (извините, у меня сейчас нет времени, просто бросаю идеи)
-
Мы можем сравнить различные веб-сайты поиска работы (Glassdoor HK и JobsDB и т. д.) по разнообразию вакансий и "качеству". В следующий раз, когда вы будете искать работу, вы будете знать, какой из них лучше. Если вы ищете аналитика данных на JobsDB и Glassdoor в Гонконге, вы поймете, о чем я говорю.
-
Вы также можете извлечь информацию о зарплате, чтобы сравнить среднюю зарплату разных должностей в разных странах. Затем вы можете сравнить с ВВП/средним доходом/индексом доступности... и увидеть, в какой стране находится рай для вашей желаемой работы.
Полный код: Используйте его, если вам нужно. Код является нерабочим, потому что я спешил вчера вечером. Если у вас есть лучший подход, пожалуйста, оставьте комментарий, чтобы мы могли вместе улучшить! Большое спасибо. (извините, что вставка кода в Medium может вызвать проблемы с отступами.)
from bs4 import BeautifulSoup
import requests
import re
import pandas as pd
import csv
import timeglobal job_title_list
job_title_list=[]
global company_list
company_list=[]
global yr_exp_list
yr_exp_list=[]
global level_list
level_list=[]
global role_list
role_list=[]
role=[]
global skill_list
skill_list=[]def get_job_id(job_name):
t1=time.time() baseurl='[https://hk.jobsdb.com/hk/search-jobs/'+re.sub('](https://hk.jobsdb.com/hk/search-jobs/'+re.sub(') ','- ',job_name)+'/1'
r=requests.get(baseurl,headers={"User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1"}).text
soup=BeautifulSoup(r,'lxml')
pattern=re.compile(r'(\d+)')
total_num_jobs=re.findall('\d+', soup.find('meta', property="og:description")['content'])
float(total_num_jobs[0]) #на самом деле только одно значение
num_page=int(total_num_jobs[0]) //30 +1 #если действительно не нужно? как в этом случае
global job_id
job_id=[]
skills = ['R', 'RStudio', 'Markdown', 'Latex', 'SparkR', 'D3', 'D3.js','Microsoft Office','Excel',
'Unix', 'Linux', 'MySQL', 'Microsoft SQL server', 'SQL','VBA','Qlik'
'Python', 'SPSS', 'SAS', 'C#','Matlab','Java', 'keras',
'JavaScript', 'HTML', 'HTML5', 'CSS', 'CSS3','PHP', 'Excel', 'Tableau',
'AWS', 'Amazon Web Services ','Google Cloud Platform', 'GCP','theano'
'Microsoft Azure', 'Azure', 'Hadoop', 'Spark','python'
'MapReduce', 'Map Reduce','Shark', 'Cassandra',
'NoSQL', 'MongoDB', 'GIS', 'Haskell', 'Scala', 'Ruby','Perl',
'Mahout', 'Stata','Deep Learning','Machine Learning', 'Pytorch', "Tensorflow",'Caffe','API','seo','Business Intelligence'
, 'BI', ]
skill_lower=[i.lower() for i in skills]
skill_set=list(set(skill_lower))
skill_set
for i in range(1,num_page+1):
#time.sleep(1)
print(f'Now start to craw job search result P.{i}')
newurl=baseurl[:-1]+str(i)
r=requests.get(newurl,headers={"User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1"}).text
soup=BeautifulSoup(r,'lxml')
script_str=soup.find_all("script")[1].get_text()[28:]pattern=re.compile(r'(https\W\Wu002F\Wu002Fhk\Wjobsdb\Wcom\Wu002Fhk\Wu002Fen\Wu002Fjob\Wu002F)([a-zA-Z0-9]+\W)*')
job_url_raw=[]
job_url_raw2=[]
for i in pattern.finditer(script_str):
job_url_raw.append(i[0])
for i in job_url_raw:
a=re.sub("u002F",'',i)
b=re.sub('"','',a)
c=b.split('\\')[-1]
print(c)
job_id.append(c)
for i in job_id:
try: #есть пустой id?
print(f'Now start to craw individual job id {i}')
joburl='[https://hk.jobsdb.com/hk/en/job/'+str(i)](https://hk.jobsdb.com/hk/en/job/'+str(i))
r=requests.get(joburl,headers={"User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1"}).text
soup=BeautifulSoup(r,'lxml')try:
job_title= soup.find(class_='general-pos ad-y-auto-txt2').text.strip()
except:
job_title= soup.find(class_='general-pos ad-y-auto-txt').text.strip()
print(f'Job title is {job_title}')try:
company= soup.find(class_='jobad-header-company').text.lstrip()
except:
company= soup.find(class_='jobad-header-company ad-y-auto-txt1').text.lstrip()
print(f'company {job_title}')role=[i.text.lower() for i in soup.find_all('span')]
role2=str(role)
print(f' Got job role for this post {job_title}')
try:
yr_exp=soup.find('b', class_='primary-meta-exp').text.strip()
yr_exp=yr_exp[0]
except:
yr_exp='Didnt specify'
print(f' Exp is {yr_exp} ')try:
level=soup.find('b', class_='primary-meta-lv').text.strip()
except:
level='Didnt specify'
print(f' level is {yr_exp} ')job_title_list.append(job_title)
company_list.append(company)
yr_exp_list.append(yr_exp)
role_list.append(role)
print('now to matching')
required_skill=[]
for i in skill_set:
result=re.search(i,role2)
if result:
required_skill.append(i)
skill_list.append(i)
else:
pass
with open ('jobsDBtest_csv_result.csv','a') as f:
csv_writer= csv.writer(f)
csv_writer.writerow([i,job_title,company,yr_exp,level,required_skill])
except:
print('Cant crawd some id')
pass
t2= time.time()
print(f'It takes {t2-t1} to crawd all the jobs info')
print(f'There are total {len(job_id)} jobs in this category') frequency={}
for item in skill_list:
frequency[item]=frequency.get(item,0)+1
skill_count=pd.Series(frequency)
skill_count.sort_values(ascending=False,inplace=True)
return skill_countget_job_id('data analyst')