基于Spider异步爬虫框架+JS动态参数逆向+隧道代理+自定义中间件的猎聘招聘数据爬取

devtools/2024/11/19 4:38:04/

在本篇博客中,我们将介绍如何使用 Scrapy 框架结合 JS 逆向技术、代理服务器和自定义中间件,来爬取猎聘网站的招聘数据。猎聘是一个国内知名的招聘平台,提供了大量的企业招聘信息和职位信息。本项目的目标是抓取指定城市的招聘信息,提取相关的职位名称、薪资、公司名称等信息。

项目结构

项目的基本结构如下:

liepin/
├── liepin/
│   ├── items.py          # 定义Item模型
│   ├── pipelines.py      # 定义数据处理管道
│   ├── settings.py       # Scrapy的配置文件
│   ├── spiders/
│   │   └── lp_spider.py  # 爬虫代码
├── lp.js                 # JS逆向代码
└── data.csv              # 存储爬取的招聘数据
  • lp_spider.py:定义了爬虫的核心逻辑,负责发起请求、解析数据并将数据传递给 pipeline 进行处理。
  • middlewares.py:自定义了爬虫和下载器中间件,处理请求的重试逻辑和代理。
  • pipelines.py:定义了数据存储的逻辑,将爬取的数据保存为 CSV 文件。
  • settings.py:配置 Scrapy 框架的各项设置。
  • lp.js:包含 JS 代码,用于生成请求所需的参数。

步骤 1: 配置代理和用户身份认证

爬虫开始前,我们首先需要配置代理服务器。为了防止 IP 被封锁,我们使用了快代理提供的代理服务器。代理认证信息包括 usernamepassword,通过将这些信息构造为代理 URL:

# 隧道域名:端口号
tunnel = "xxxx"# 用户名密码方式
username = "xxxx"
password = "xxxx"proxy_url = f"http://{username}:{password}@{tunnel}"

然后,我们将在后续请求中使用该代理 URL。

步骤 2: 使用 JS 逆向技术生成请求参数

猎聘的 API 请求需要一个动态生成的 ckId 参数,这是通过执行 JavaScript 代码来生成的。为了获取这个参数,我们通过 Python 的 execjs 库来执行 JS 代码。

js_file = open('../lp.js', mode='r', encoding='utf-8').read()
js_code = execjs.compile(js_file)
ckId = js_code.call('r', 32)

其中,r 是 JS 代码中的一个函数,用于生成一个随机的 ckId 值。这样可以模拟正常用户请求,避免被反爬虫机制拦截。

步骤 3: 编写爬虫逻辑

爬虫的核心是发起请求并解析返回的数据。我们定义了一个名为 LpSpider爬虫类,继承自 Scrapy 的 Spider 类。

class LpSpider(scrapy.Spider):name = "lp"custom_settings = {'RETRY_ENABLED': True,'RETRY_TIMES': 3,'RETRY_HTTP_CODES': []}def start_requests(self):for city in city_list:for key in key_word:for page in range(0, 10):js_code = execjs.compile(js_file)ckId = js_code.call('r', 32)data = { ... }  # 构造请求数据json_data = json.dumps(data)url = 'https://api-c.liepin.com/api/com.liepin.searchfront4c.pc-search-job'headers = { ... }  # 请求头部信息yield scrapy.Request(url=url, method='POST', headers=headers, body=json_data, callback=self.parse, meta={'proxy': proxy_url})

步骤 4: 解析响应数据

当服务器返回职位数据时,我们需要提取相关信息。主要的数据包括职位名称、薪资、公司信息等。在 parse 方法中,我们从 JSON 响应中提取数据,并将每个职位的详情链接传递给 parse_details_page 方法。

def parse(self, response):data = json.loads(response.body)job_card_list = data['data']['data']['jobCardList']for job_card in job_card_list:job_link = job_card['job']['link']yield scrapy.Request(url=job_link, callback=self.parse_details_page, meta={'proxy': proxy_url})

步骤 5: 解析职位详情页

在职位详情页中,我们进一步提取职位的详细信息,如公司介绍、职位描述等。

def parse_details_page(self, response):try:item = LiepinItem()item['title'] = response.xpath('//span[@class="name ellipsis-2"]/text()').get()item['salary'] = response.xpath('//span[@class="salary"]/text()').get()item['company_name'] = response.xpath('//div[@class="name ellipsis-1"]/text()').get()item['company_intro'] = response.xpath('//div[@class="inner ellipsis-3"]/text()').get()item['job_intro'] = response.xpath('//dd[@data-selector="job-intro-content"]/text()').get()item['job_loca'] = response.xpath('/html/body/section[3]/div[1]/div[3]/span[1]/text()').get()item['job_exp'] = response.xpath('/html/body/section[3]/div[1]/div[3]/span[3]/text()').get()item['job_educate'] = response.xpath('/html/body/section[3]/div[1]/div[3]/span[5]/text()').get()key_word_elements = response.xpath('//div[@class="tag-box"]/ul/li/text()')item['key_word_list'] = [kw.get() for kw in key_word_elements]item['company_info'] = [response.xpath('//div[@class="company-other"]/div[1]/span[@class="text"]/text()').get(),response.xpath('//div[@class="company-other"]/div[2]/span[@class="text"]/text()').get(),response.xpath('//div[@class="company-other"]/div[3]/span[@class="text"]/text()').get(),]item['details_url'] = response.urlyield itemexcept Exception as e:self.logger.error(f"An error occurred: {e}")

步骤 6: 配置重试和延迟

由于爬虫在运行时可能会遇到网络错误或被目标网站屏蔽,因此我们需要实现请求的重试逻辑。我们通过自定义重试中间件来实现该功能。

class CustomRetryMiddleware(RetryMiddleware):def process_exception(self, request, exception, spider):if isinstance(exception, self.EXCEPTIONS_TO_RETRY):retry_times = request.meta.get('retry_times', 0) + 1if retry_times <= self.max_retry_times:retryreq = self._retry(request, exception, spider)if retryreq is not None:retryreq.meta['retry_times'] = retry_timesreturn retryreq

步骤 7: 数据存储

抓取到的职位信息通过 Scrapy 的 pipeline 存储到 CSV 文件中。我们定义了一个 LiepinPipeline 类来处理数据存储。

class LiepinPipeline:def __init__(self):self.file = open('data.csv', 'a', newline='', encoding='utf-8')self.writer = csv.writer(self.file)self.writer.writerow(['Job Name', 'Salary', 'Company Name', 'Company Intro', 'Job Intro', 'Job Location', 'Job Experience', 'Job Education', 'Key Word List', 'Company Info', 'Details URL'])def process_item(self, item, spider):self.writer.writerow([item['title'], item['salary'], item['company_name'], item['company_intro'], item['job_intro'], item['job_loca'], item['job_exp'], item['job_educate'], item['key_word_list'], item['company_info'], item['details_url']])return itemdef close_spider(self, spider):self.file.close()

将爬取失败的代码采用selenium进行重采(登录后)

代码的主要流程如下:

  1. 读取失败的请求URL
    我们从一个文件failed_requests.txt中读取之前失败的URL,并存储在failed_requests列表中。

  2. 配置Selenium WebDriver
    我们使用Chrome浏览器作为Selenium的驱动程序,通过webdriver.Chrome()初始化浏览器实例。设置隐式等待时间(implicitly_wait(3))来处理网页加载的延迟。

  3. 处理每个失败的请求
    对于每个失败的URL,我们重新访问该页面。为了避免频繁的登录,我们使用一个标志has_logged_in来判断是否已经登录。如果没有登录,我们手动提示登录操作。

  4. 提取网页数据
    使用Selenium获取当前页面的源代码后,使用lxml.etree解析HTML。通过XPath选择器,我们提取职位的相关信息,如职位标题、薪资、公司介绍、职位要求等。

    • 职位标题tree.xpath('/html/body/section[3]/div[1]/div[1]/span[1]/span/text()')
    • 薪资tree.xpath('//span[@class="salary"]/text()')
    • 公司信息:包括公司名称、公司介绍等,均通过XPath进行提取。
  5. 处理提取错误
    使用try-except语句来处理数据提取过程中可能出现的错误,避免程序中断。即使某个字段没有数据,代码仍然会继续运行,确保尽可能多地提取到有效数据。

  6. 将数据写入CSV
    提取的数据被按行写入CSV文件,包含公司名称、职位信息、薪资、工作要求等字段。

  7. 关闭WebDriver
    在所有请求处理完毕后,调用driver.quit()关闭浏览器实例,释放资源。

代码示例
# 从文件中读取失败的请求URL
with open('failed_requests.txt') as f:failed_requests = [line.strip() for line in f.readlines()]# 设置Selenium WebDriver
driver = webdriver.Chrome()
driver.implicitly_wait(3)has_logged_in = False# 打开CSV文件用于写入数据
with open('failed_requests_data.csv', mode='w', newline='', encoding='utf-8') as file:writer = csv.writer(file)# 写入表头writer.writerow(['Company Name', 'Company Intro', 'Job Intro', 'Job Location', 'Job Experience', 'Job Education', 'Title','Salary', 'Key Words', 'Company Info', 'Details URL'])for failed_request in failed_requests:driver.get(failed_request)if not has_logged_in:input('请登录')has_logged_in = True# 提取数据title = tree.xpath('/html/body/section[3]/div[1]/div[1]/span[1]/span/text()')[0] if tree.xpath('/html/body/section[3]/div[1]/div[1]/span[1]/span/text()') else ''salary = tree.xpath('//span[@class="salary"]/text()')[0] if tree.xpath('//span[@class="salary"]/text()') else ''company_name = tree.xpath('//div[@class="name ellipsis-1"]/text()')[0] if tree.xpath('//div[@class="name ellipsis-1"]/text()') else ''company_intro = tree.xpath('//div[@class="inner ellipsis-3"]/text()')[0] if tree.xpath('//div[@class="inner ellipsis-3"]/text()') else ''job_intro = tree.xpath('//dd[@data-selector="job-intro-content"]/text()')[0] if tree.xpath('//dd[@data-selector="job-intro-content"]/text()') else ''job_loca = tree.xpath('/html/body/section[3]/div[1]/div[3]/span[1]/text()')[0] if tree.xpath('/html/body/section[3]/div[1]/div[3]/span[1]/text()') else ''# 关闭WebDriver
driver.quit()
代码说明
  • WebDriverWait:用于等待网页中的某个元素加载完成,避免程序在页面未加载完毕时就进行数据提取。
  • XPath提取tree.xpath()用于从HTML中提取相关数据,XPath的使用使得提取过程更加灵活和精确。
  • CSV写入:提取到的数据被写入到CSV文件中,方便后续分析。

最后爬取的数据结果

在这里插入图片描述

总结

本项目通过 Scrapy 框架结合 JS 逆向技术和自定义中间件,成功地爬取了猎聘招聘平台的数据,并存储在本地 CSV 文件中。重试机制和代理设置保证了爬虫的稳定性和反爬虫防护。该方案适用于类似需要绕过反爬虫机制的招聘网站或其他数据来源。

如果你对 Web 爬虫的其他技术和最佳实践感兴趣,欢迎关注本博客。

需要源代码的添加我

在这里插入图片描述


http://www.ppmy.cn/devtools/135122.html

相关文章

Vue 与 React 前端框架差异对比及案例分析

一、设计理念 1.Vue&#xff1a; Vue 被设计为渐进式框架&#xff0c;能够自底向上逐层应用。这意味着可以将其灵活地应用于现有项目的一部分&#xff0c;无需对整个项目进行大规模重构。强调数据驱动视图&#xff0c;通过响应式数据绑定&#xff0c;当数据发生变化时&#x…

SobarQube实现PDF报告导出

文章目录 前言一、插件配置二、使用步骤1.新生成一个Token2.将拷贝的Token加到上文中执行的命令中3.查看报告 三、友情提示总结 前言 这篇博文是承接此文 .Net项目在Windows中使用sonarqube进行代码质量扫描的详细操作配置 描述如何导出PDF报告 众所周知&#xff0c;导出PDF功…

Rust 布尔类型

文章目录 1、基本用法2、实例 bool 代表一个值&#xff0c;它只能是 true 或 false。 如果将 bool 转换为整数&#xff0c;则 true 将为 1&#xff0c;false 将为 0. 1、基本用法 bool 实现了各种 traits&#xff0c;例如 BitAnd、BitOr、Not 等&#xff0c;允许我们使用 &…

ollama+springboot ai+vue+elementUI整合

1. 下载安装ollama (1) 官网下载地址&#xff1a;https://github.com/ollama/ollama 这里以window版本为主&#xff0c;下载链接为&#xff1a;https://ollama.com/download/OllamaSetup.exe。 安装完毕后&#xff0c;桌面小图标有一个小图标&#xff0c;表示已安装成功&…

SpringBoot整合FreeMarker生成word表格文件

SpringBoot整合FreeMarker生成word表格文件&#xff08;使用FTL模板&#xff09;_freemarker ftl模板-CSDN博客 Freemarker基本指令语法和集合指令语法SpringBoot整合FreeMarker生成word表格文件&#xff08;使用FTL模板&#xff09;_freemarker ftl模板-CSDN博客https://zhua…

Python自动化小技巧24——实现自动化输出模板表格报告

背景 很多人拿到数据excel文件&#xff0c;然后要写报告&#xff0c;做表格&#xff0c;要各种计算&#xff0c;各种排序&#xff0c;分组聚合&#xff0c;数据透视&#xff0c;然后合并单元格&#xff0c;添加边框&#xff0c;加粗&#xff0c;添加显示规则&#xff0c;添加数…

数据分析编程:SQL,Python or SPL?

Talk is cheap. Let’s show the code 1. 计算用户会话次数 用户行为数据表 useridaction_typeaction_timeU1059login2023-12-01 18:00:10U1092login2023-12-01 18:00:17U1069login2023-12-01 18:00:22……… 10 分钟没有任何动作或退出后 5 分钟没有登录则认为会话结束&am…

matlab 读取csv

需要跳过第一行表头等信息 1、读取整个文件 csvread(FILENAME)%文件路径 文件名2、指定起始位置 csvread(FILENAME, R, C)%从文件的第R行和第C列开始读取数据 逗号分开3、指定数据范围 csvread(FILENAME, R, C, [R1 C1 R2 C2])%读取从(R1, C1)到(R2, C2)范围内的数据注意&am…