概要
最近学起了python爬虫,感觉入门挺容易的,甚至还有点好玩,于是试了下用来爬取拉勾网以练练手(其实也试过boss直聘的,由于boos的防爬太强,就放弃了。。)。
主要功能是,在特定的筛选条件遍历和获取每个工作职位信息并保存到MongoDB中。
环境
python的依赖包使用pipenv
安装
- Python 3.7.5
- selenium 3.141
- Chrome 79
- MongoDB 4.2
实例代码
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium import webdriver
from lxml import etree
import pymongo
import random
import time
import re
class WantYou:
def __init__(self):
self.mongo = pymongo.MongoClient('mongodb://localhost:27017/')
self.mongo_db = self.mongo['lagou']
self.use_chrome_browser()
def use_chrome_browser(self):
driver_path = r'D:\APP\chromedriver\chromedriver.exe'
self.browser = webdriver.Chrome(executable_path=driver_path)
def start(self):
"""
主体流程,包括:设置筛选条件、点击跳转详情、翻页等
:return:
"""
# 第2条url自带query参数可以免去1、2、3步设置过滤条件操作,这里因为练习需要使用简版url
list_url = 'https://www.lagou.com/zhaopin/Python/?labelWords=label'
# list_url = 'https://www.lagou.com/jobs/list_Python/p-city_213-gm_6?px=default&yx=50k%E4%BB%A5%E4%B8%8A#filterBox'
self.browser.get(list_url)
element = WebDriverWait(self.browser, 10).until(
EC.visibility_of_element_located((By.CLASS_NAME, 'salary'))
)
print('salary text:', element.text)
# 如果出现红包弹层,那关闭它
time.sleep(0.5)
if self.is_element_exist(By.CSS_SELECTOR, '.body-container .body-btn'):
self.browser.find_element_by_css_selector('.body-container .body-btn').click()
print('关闭红包弹层')
# 设置过滤条件,如果不需要请注释掉
self.filter()
page_count = 0
job_count = 0
while True:
page_count += 1
# 处理并记录当前列表信息到数据库
self.parse_list_page(self.browser.page_source)
# 遍历打开第一个工作的详情面,新标签跳转,h3标签即是职位名,点击可跳转
for i in self.browser.find_elements_by_css_selector('h3'):
job_count += 1
print('当前:(%s)页,(%s)条:[%s]' % (page_count, job_count, i.text), '》' * 10)
# 点击详情链接,会自动跳转到新标签打开页面
i.click()
# 切换新标签为当前标签
self.browser.switch_to.window(self.browser.window_handles[1])
WebDriverWait(self.browser, 10).until(
EC.visibility_of_element_located((By.CLASS_NAME, 'job-detail'))
)
# 处理并记录详情页的信息,补充添加到数据库
self.parse_detail_page(self.browser.page_source)
# 关闭新标签
self.browser.close()
# 切换当前标签为列表页所在的标签
self.browser.switch_to.window(self.browser.window_handles[0])
# 获取下一页按钮,当可用时,点击跳转下一页
if not self.is_element_exist(By.CSS_SELECTOR, '.pager_next.pager_next_disabled'):
self.browser.find_element_by_css_selector('.pager_next').click()
WebDriverWait(self.browser, 10).until(
EC.visibility_of_element_located((By.CLASS_NAME, 'salary'))
)
time.sleep(1)
print('next page')
else:
break
print('顺利完成,共(%s)页(%s)条' % (page_count, job_count))
def filter(self):
"""
设置过滤条件
:return:
"""
# 1.设置城市筛选条件
if self.is_element_exist(By.CSS_SELECTOR, '.city-wrapper [data-id="763"]'):
# 因为所处的网络位置问题,有时候默认就是广州,因此只有默认非广州时才设置本条件
time.sleep(random.uniform(0.5, 1.5))
# 763是广州,765是深圳
self.browser.find_element_by_css_selector('.city-wrapper [data-id="763"]').click()
WebDriverWait(self.browser, 10).until(
# 上一步会造成页面刷新,这一步主要是等待页面加载完成,检测的是哪个元素不重要
EC.visibility_of_element_located((By.CLASS_NAME, 'salary'))
)
print('城市条件完成')
# 2.设置薪酬筛选条件
time.sleep(random.uniform(0.5, 1.5))
# 展开下拉框
self.browser.find_element_by_css_selector('.salary .text').click()
# 选择指定的条件,这里选最后一个
self.browser.find_element_by_css_selector('.salary li:last-of-type > a').click()
WebDriverWait(self.browser, 10).until(
EC.visibility_of_element_located((By.CLASS_NAME, 'salary'))
)
print('薪酬条件完成')
# 3.设置公司规模筛选条件
time.sleep(random.uniform(0.5, 1.5))
self.browser.find_element_by_css_selector('li.multi-chosen:nth-of-type(4) > a:last-of-type').click()
WebDriverWait(self.browser, 10).until(
EC.visibility_of_element_located((By.CLASS_NAME, 'salary'))
)
print('规模条件完成')
def parse_list_page(self, page_source):
"""
:param page_source: 列表页的html字串
:return:
"""
page = etree.HTML(page_source)
jobs = page.xpath('//li[contains(@class,"con_list_item")]')
jobs_info = []
for i in jobs:
item = {
'job_id': i.xpath('./@data-positionid')[0],
'company_id': i.xpath('./@data-companyid')[0],
'company_name': i.xpath('./@data-company')[0],
'name': i.xpath('./@data-positionname')[0],
'salary': i.xpath('./@data-salary')[0],
# 'company_name': i.xpath('string(.//div[@class="company_name"]//a)'),
# 'name': i.xpath('string(.//h3)'),
# 'salary': i.xpath('string(.//span[@class="money"])'),
'district': i.xpath('string(//span[@class="add"])').strip('[]'),
'industry': i.xpath('string(.//div[@class="industry"])').strip(),
'logo': i.xpath('.//div[@class="com_logo"]//img/@src')[0],
'link': i.xpath('.//a[@class="position_link"]/@href')[0],
'tags': i.xpath('.//div[@class="list_item_bot"]//span/text()'),
'advantage': i.xpath('.//div[@class="list_item_bot"]/div[last()]/text()')[0].strip('“”'),
'post_at': i.xpath('.//span[@class="format-time"]/text()')[0],
}
exp, degree = ''.join(i.xpath('.//div[@class="p_bot"]/div/text()')).strip().split(' / ')
item['exp'] = exp
item['degree'] = degree
jobs_info.append(item)
print('列表页获得数据:', jobs_info)
# 将获取的每一条招聘信息,保存到mongo
self.mongo_db['jobs'].insert_many(jobs_info)
def parse_detail_page(self, page_source):
"""
:param page_source: 详情页的html字串
:return:
"""
# 保存详情页信息补充增加到mongo
page = etree.HTML(page_source)
job_detail = page.xpath('//dl[@id="job_detail"]')[0]
# print(job_detail)
item = {
'description': job_detail.xpath('string(.//div[@class="job-detail"])'),
'address': re.sub(r'[\s|查看地图]', '', job_detail.xpath('string(.//div[@class="work_addr"])')),
'position_lng': job_detail.xpath('.//input[@name="positionLng"]/@value')[0],
'position_lat': job_detail.xpath('.//input[@name="positionLat"]/@value')[0],
}
job_id = page.xpath('.//input[@id="jobid"]/@value')[0]
print('详情页获得数据:', item)
self.mongo_db['jobs'].update_one({'job_id': job_id}, {'$set': item})
def is_element_exist(self, by, value):
"""
用来判断标签元素是否存在
:param by: 条件类型
:param value: 条件内容
:return:
"""
try:
self.browser.find_element(by=by, value=value)
except NoSuchElementException as e:
# 发生了NoSuchElementException异常,说明页面中未找到该元素,返回False
return False
else:
return True
if __name__ == '__main__':
WantYou().start()
获得的数据
最后
以上内容是偏基础的练习性操作,可能并非最优解,如有补充,欢迎留言。