selennium实战详解

目录

前言

1.需求分析

1.1该要什么

1.2分析招聘信息

1.2.1查看一级页面信息

1.2.2查看二级页面信息

 1.2.3查看下一页信息(涉及AJAX反爬)

1.3爬取思路

1.4(附)selenium使用教程

2.项目实施

2.1环境准备

2.2设计工程

2.2.1创建爬虫文件

 2.2.2构建driver对象

2.2.3构建访问入口函数

 2.2.4构建爬取一级页面信息函数

2.2.5构建driver设置函数

2.2.6构建爬取二级页面数据函数

2.2.7定义二级页面访问接口

2.2.8 定义翻页函数

2.2.9构建保存数据函数

 3.完整代码

4.案例反思


前言

为提升我们获取信息的综合能力,我们以反爬机制较强的拉勾网为例,讲解如何使用反反爬技术获取招聘网站的全面信息。

今天是2021年11月23号,我仍在编辑调试着这篇文章,可今晚发现,wc拉勾更新了,不同的源码,更新的排版,我一时说不上话,看来我要损失好一部分代码了,不容易想到双driver实现看来要没了..

也许人生就是这样吧。

果不其然,这次拉勾的反爬又升级了,现在连二级页面的网址也不给。为了爬取更全面的信息,我会花尽可能短的时间用selenium获取拉勾网详细招聘信息并讲解。

1.需求分析

为了能获取拉勾网的招聘信息,我们首先应该做的就是需求分析。

1.1该要什么

对于即将步入社会的我们,当然要知道未来就业公司状况,对于我来说首先呢就是查看岗位要求,学历要求确定自己是否符合条件,其次就是薪水范围再者就是工作地址等等。

1.2分析招聘信息

1.2.1查看一级页面信息

开始访问拉勾网https://www.lagou.com/,可以看到首页提供的职位还是蛮多的。

selennium实战详解_第1张图片

点击搜索框搜索java,选中一个岗位名称后点击右键->检查,岗位名称可在右侧找到。同理我们选中月薪,经验要求,公司名称等都能在右侧相应位置找到。

selennium实战详解_第2张图片

往上找到div[@id="jobList"]节点,往下发现

selennium实战详解_第3张图片

每一个'item_10RTO'对应一个招聘信息

selennium实战详解_第4张图片

 每个招聘信息结构都大致相似,因此找出其中的规律便可爬取相关信息 

 selennium实战详解_第5张图片

获取首页信息是不够的,比如没有岗位的具体要求和地址,所以我们要进入招聘信息的二级页面。在这之前我们要先找一下这里的源码有无二级页面的网址,这对接下来的步骤起很关键的作用。

selennium实战详解_第6张图片

(旧版拉勾)

可以看到,旧版拉勾是可以获取招聘信息的二级页面网址,之后可以直接访问爬取。而新版没有,反倒给出了一个有但不全面的信息浮窗,鼠标要停留在信息元素上面时才能显示,稍微移动就没有不显示,而且信息不全。这给爬取增加了一定的难度。

selennium实战详解_第7张图片

暂时不管它,点击浮窗进入二级页面。

1.2.2查看二级页面信息

进入二级页面,比较庆幸的是新版拉勾没有改动二级界面结构,可以直接用更新前的代码爬取。

这里有职位诱惑,职位描述,和工作地址。职位诱惑在一级页面可以获取我就不再多述。selennium实战详解_第8张图片

老规矩,分别选中职位描述和工作地址查看可以发现源码都是存在的,可以直接获取。

selennium实战详解_第9张图片

 1.2.3查看下一页信息(涉及AJAX反爬)

我们回到一级页面,滑到底部选择下一页右击检查,发现这里没有下一页的地址!

selennium实战详解_第10张图片

因为招聘网采用了AJAX技术,实现了前后端分离,并且请求参数以POST表单的形式提交。如果使用scrapy的话需要进一步分析招聘列表获取动态加载招聘列表的数据接口之后才能获取下一页数据。过程较为复杂,不容易理解,因此我选择了selenium。用selenium的话直接发生点击下一页的动作即可。(今天是11.28,我突然发现拉勾网切换至下一页后网址会出现码数特征,就是说可以直接通过改变规律地址的方式来切换至自己想要的页数,而之前的话是没有的,之前的网址无论怎么切换,都是第一页的地址,所以才有了后续的翻页操作)

但如果直接在拉勾网首页进入一个岗位的话,会有下一页网址,比较简单,但没必要

selennium实战详解_第11张图片selennium实战详解_第12张图片

 因为selenium,点击,输入那些动作都是轻而易举的,既然用了它,那么搞的华丽一点又何妨呢。

1.3爬取思路

 我们知道了如何获取每一部分的招聘信息,现在就把思路串联起来。

使用selenium访问拉勾网首页,在搜索框搜索自己心仪的岗位,等跳转到一级页面后使用selenium的find_elements的方法获取职位名称,月薪,公司名称等,然后点击招聘名称访问二级页面,继续用find_elements方法获取岗位需求和工作地址。爬取完成后点击下一页重复上面的步骤进行爬取。至于selenium的使用,可以看一下这篇文档。

1.4(附)selenium使用教程

对于selenium还不太了解的同学可以看一下这篇文章,可以让你基本入门。

selenium的配置与使用_lishuaigell的博客-CSDN博客来,看了你就会https://blog.csdn.net/lishuaigell/article/details/121451195

2.项目实施

2.1环境准备

PyCharm Community Edition 2021.2.1,selenium4.0.+

2.2设计工程

2.2.1创建爬虫文件

进入pycharm,找到右上角file,点击new project,创建LagouEmployment_CSDN文件。

selennium实战详解_第13张图片

之后就进入到了我们熟悉的界面。

selennium实战详解_第14张图片

 2.2.2构建driver对象

构建driver对象我们必须有要访问的目标网址,和EdgeDriver的路径,使用webdriver时要导入webdriver包。因为实现后续每一步操作都要使用driver,所以在主函数上面定义。前面说过,我们爬取招聘信息是要在搜索框输入岗位实现自定义访问,所以我们要用input接收想要爬取的字符串。

kw = input('请输入您要想查找的岗位:')

这个交互要放在哪里好呢?适宜放在driver启动前。因为driver启动后会弹出一个浏览器,影响操作,所以要在他弹出前对kw赋值。这里的代码就是:

# 用这个路径导包可以正常使用,用pycharm推荐路径导包可能会执行出错
from selenium import webdriver

if __name__ == '__main__':
    url = 'https://www.lagou.com/'
    driver_path = 'C:\edgedriver_win64\msedgedriver.exe'
    # 自定义查询岗位
    kw = input('请输入您要想查找的岗位:')
    # 启动driver
    driver = webdriver.Edge(driver_path)

2.2.3构建访问入口函数

构建一个名为entrance的访问入口函数,用来进入拉勾网和搜索岗位。注意:拉勾网有严格的反爬机制,为模拟人访问,我们每执行一些代码时应停止操作一段时间。所以要引进time.sleep函数。现在看一下用selenium拉勾网跟平时我们访问的有什么不同。

# url = 'https://www.lagou.com/' 在主函数上创建的url
driver.get(url)

发现会弹出切换城市的浮窗。因为selenium开启的是一个全新纯净的浏览器,所以弹出的是初次访问拉勾的页面。

selennium实战详解_第15张图片

 为此我们要关闭浮窗才能进行下一步操作。

选择一个心仪的城市,我的话要么选广州,要么选深圳。因为深圳岗位多一点,工资高一点所以我选择深圳。对深圳右击检查,确定源码中位置(而新版拉勾这里选择城市之后又会失效变为全国)

selennium实战详解_第16张图片复制xpath路径

selennium实战详解_第17张图片

然后使用find_element_xpath()的方法查找元素,并click()点击元素

driver.find_element_by_xpath('//*[@id="changeCityBox"]/ul/li[6]/a').click()  # 切换至深圳站,因为深圳IT岗位较多

之后进入到首页,为了能输入自定义字符串,我们右击检查搜索框

selennium实战详解_第18张图片

 发现有‘input’字段,那就说明我们找对了,

selennium实战详解_第19张图片

 现在我们就要定位这个元素位置,用send_keys方法向其传入字符串。这次不用xpath查找,换另外一种方法。

    ipt = driver.find_element_by_id("search_input")
    ipt.send_keys(kws)

传入字符串后接着就是点击搜索了。我们右击检查搜索

selennium实战详解_第20张图片

 确定元素位置后click()一下,就能搜索了。完整代码如下:

import time
def entrance(urls, kws):
    driver.get(urls)
    time.sleep(2)
    # 注意elements 与 element的区别 elements返回的是列表,无法点击
    driver.find_element_by_xpath('//*[@id="changeCityBox"]/ul/li[6]/a').click()  # 切换至深圳站,因为深圳IT岗位较多
    ipt = driver.find_element_by_id("search_input")
    ipt.send_keys(kws)
    time.sleep(2)
    driver.find_element_by_id("search_button").click()
    time.sleep(2)

注意:entrancce的两个参数由主函数传递进来。每执行一段程序建议休眠一段时间,休眠时长根据自身网络配置自行调整。

主函数添加这行代码:

selennium实战详解_第21张图片

 运行后就能对进行拉勾初步访问了

selennium实战详解_第22张图片

 2.2.4构建爬取一级页面信息函数

构建名为get_information的函数,用于爬取一级页面的招聘信息。现在,我要爬取这些数据

岗位名称, 月薪,公司名称,公司福利,经验要求,学历要求六项数据。

首先选中岗位名称右键检查发现这个元素的父节点在class名为‘p-top_1F7CL’ 的节点下

selennium实战详解_第23张图片

我们复制名称发现可以找到15个元素,有爬取经验的话你就会发现这里刚好是一页的数据,所以我们名称可以通过这个节点查找,至于方法,即可用xpath也可用class_name的方法。同理,我们公司名,福利,月薪等都能用这个方式获取,唯一不同的是

selennium实战详解_第24张图片

经验跟学历在组成同一个文本,要用split()的方法对他们进行分割。而这以这两者的合并的字符串文本又跟月薪的父节点在同一节点下,但月薪有标签,经验学历要求没有。之前我们说过:如果使用selenium的find_element的方法获取目标路径的文本会获取递归获取所有文本,包括子节点的文本。也就是说获取经验学历要求必须通过获取div节点位置再以.text形式输出,但输出的内容不仅有经验学历要求,还有月薪,因此要用replace函数对数据进行去重。

    # 获取该节点的所有文本,其中包括有月薪,学历要求, 工作要求
    info = driver.find_elements_by_xpath('//div[@class="p-bom__JlNur"]')
    salary = driver.find_elements_by_xpath('//div[@class="p-bom__JlNur"]/span')
    # 因为月薪是跟学历/工作要求的父节点在同一父节点下的文本文件,而selenium的.text方法是提取目标路径下的所有文本,包括子节点的文本,所以会有数据重复,要去重
    for i in range(len(name)):  # len(name)表示有多少条招聘信息
        s = info[i].text.replace(salary[i].text, "")
        edu_requirements = s.split(" / ")[0]
        exp_requirements = s.split(" / ")[1]

 所以现在获取一级页面的招聘信息的任务就结束了。完整的代码如下:

def get_information():
    # 1.selenium的find_elements_by_xpath不支持直接获取目标属性值
    # 2.因为一页不止一个招聘数据,所以不能用element只能用elements
    # 3.elements里面的元素是selenium标记的元素地址
    # 4.因为elements返回的是列表,而列表没有get_attribute属性,所以要获取所有与目标属性相同节点下的路径,再对其遍历
    name = driver.find_elements_by_xpath('//div[@class="p-top__1F7CL"]/a')
    companyName = driver.find_elements_by_xpath('//div[@class="company-name__2-SjF"]/a')
    welfare = driver.find_elements_by_xpath('//div[@class="il__3lk85"]')
    # 获取该节点的所有文本,其中包括有月薪,学历要求, 工作要求
    info = driver.find_elements_by_xpath('//div[@class="p-bom__JlNur"]')
    salary = driver.find_elements_by_xpath('//div[@class="p-bom__JlNur"]/span')
    # 因为月薪是跟学历/工作要求的父节点在同一父节点下的文本文件,而selenium的.text方法是提取目标路径下的所有文本,包括子节点的文本,所以会有数据重复,要去重
    for i in range(len(name)):  # len(name)表示有多少条招聘信息
        s = info[i].text.replace(salary[i].text, "")
        edu_requirements = s.split(" / ")[0]
        exp_requirements = s.split(" / ")[1]
        print(name[i].text, companyName[i].text, welfare[i].text, salary[i].text, edu_requirements,
              exp_requirements)

 完成这部分的代码后要在entrance函数那里添加这个函数进去就能被调用了。

selennium实战详解_第25张图片

 附:如果你闲着无聊想爬一下浮窗的信息

selennium实战详解_第26张图片

我可以满足你,你可以参考一下这段代码 

    # # 鼠标悬停至招聘信息中,获取二级页面简略信息
    # move = driver.find_elements_by_xpath('//div[@class="item__10RTO"]')
    # h = 50
    # for i in move:
    #     print(move.index(i))
    #     # driver.execute_script("arguments[0].scrollIntoView();", i)  # 将滚动条移动到可以显示元素位置
    #     ActionChains(driver).move_to_element(i).perform()
    #     time.sleep(2)
    #     j = driver.find_element_by_xpath('//div[@class="job_info__rB-X6 job_desr_max__N_INq"]')
    #     print(j.text)
    #     time.sleep(2)
    #     # 滑动滚动条,参数可以根据窗体大小适当调整scroll(左边距,上边距)
    #     driver.execute_script("scroll(0,%s)" % str(100 + h))
    #     h += 100

他出现弹窗的条件还是比较苛刻的,要鼠标停留在这条招聘信息才能显示,而且要在可视范围。就以上面那张图为例,就显示了三条招聘信息,所以最多只能停留在三个位置获取浮窗信息然后就要滑动滚动条了。上面代码有两种方法可以滑动,第一个就是用专门滑动的方法将滚动条定位在元素出现的位置。我用的话很鸡肋,经常对不上。第二种就是自定义增加滑动尺度了,这个不够智能,也许你要调试很多次才能找到每次元素向下移动的距离。这里我附上我成功爬下来的截图。

selennium实战详解_第27张图片

浮窗信息会损失一部分内容,即使获取下来也没有太大用处 。

2.2.5构建driver设置函数

构建名为driver_setting的函数,用于设置driver相关参数。我们每次确定爬取是否成功都要看他是否能输出招聘信息,而driver初始化是在左上角往下,会挡住控制台输出,所以我们设置一下他的初始大小跟初始位置。

def driver_setting():
    # 缩放也要注意格式,不然发生点击事件时可能会报错
    # 设置初始位置
    driver.set_window_position(x=500, y=0)
    # 设置初始大小
    driver.set_window_size(width=1050, height=900, windowHandle='current')
    # 设置全屏
    # driver.maximize_window()

 然后在主函数添加这个设置即可。

selennium实战详解_第28张图片那么下次启动后将会出现在屏幕右侧。

selennium实战详解_第29张图片

2.2.6构建爬取二级页面数据函数

构建名为detail_info函数用于爬取二级页面详情信息。现在进入二级页面,在这里我们爬取

职位描述跟工作地点。

selennium实战详解_第30张图片

 选中职位描述右击检查发现这些信息在class名为‘job-detail’的节点下。找到xpath路径之后用find_element().text的方法即可获取该节点下的所有文本信息

selennium实战详解_第31张图片

 我们多看几个详情信息会发现有些节点是在class名为‘job-detail’的节点下面还有一个节点p英所以要爬取两种情况的信息,然后舍弃掉长度为0的信息。对于地址信息,可以获取名为"work_addr"的class节点下的所有信息然后去掉最后5个字符‘\n查看地图’selennium实战详解_第32张图片

 完整代码如下:

def detail_info():
    print("successfully loading")
    # 获取'//div[@class="work_addr"]'下的文本。跟xpath不同的是selenium递归获取该节点所有文本包括节点下面节点的文本;同一根节点下的所有文本归为一个文本元素
    ad = driver.find_elements_by_xpath('//div[@class="work_addr"]')  # 详细地址
    for i in ad:  # 即使elements里面只有一个元素也不能直接ad.text获取元素文本信息
        address = i.text.strip()[:-5]  # 除去最后的'\n查看地图'

    # 因为职位描述xpath路径不一,所以要判断有可能出现的情况
    information1 = driver.find_elements_by_xpath('//div[@class="job-detail"]')
    information2 = driver.find_elements_by_xpath('//div[@class="job-detail"]/p')
    information = ""
    # 对职位描述进行拼接
    if information1:
        for i in information1:
            information += i.text.strip()
    else:
        for i in information2:
            information += i.text.strip()
    print(address)
    print(information)

2.2.7定义二级页面访问接口

定义名为relay的函数,它是一级页面访问二级页面的接口。另外定义这个接口是为了提高加载速度。我们用了句柄。先同时加载一页的所有二级界面,然后再切换句柄进行爬取并关闭。在使用for点击name的时候用了try-except避免没有加载成功的情况。完整代码如下:

#  定义二级页面访问接口
def relay(name):
    # 句柄确定主界面,即一级界面
    first_handle = driver.current_window_handle
    #  点击访问二级页面
    for i in name:
        # 若没有点击成功,则休眠两秒继续点击
        try:
            i.click()
        except:
            time.sleep(2)
            i.click()
    time.sleep(1)
    all_handles = driver.window_handles
    # 遍历所有句柄进行爬取
    for handle in all_handles:
        if handle != first_handle:
            # 切换句柄
            driver.switch_to.window(handle)
            # 爬取二级页面信息
            detail_info()
            time.sleep(1)
            # 爬取成功后关闭句柄
            driver.close()
    time.sleep(2)
    # 等所有二级界面爬取完后返回一级界面
    driver.switch_to.window(first_handle)

然后在get_information声明调用。

selennium实战详解_第33张图片

2.2.8 定义翻页函数

构建名为next_page的函数,用于实现翻页功能。为模拟翻页,我们要找到翻页的那个按钮然后点击它。右击检查下一页按钮找到该元素在源码中的位置。selennium实战详解_第34张图片

 然后点击即可。注意:点击翻页的过程中可能会因为出现浮窗,浏览器缩放,元素未更新等情况导致点击失败。所以要用time.sleep+滑动滚动条或者调整浏览器大小的方法避免出现上述情况。点击完下一页后调用get_information函数继续爬取招聘信息。当没有找到元素时说明已经到了尾页,没有下一页了,此时用try-except捕获异常并输出

具体代码如下:

def next_page():
    # driver.find_element_by_class_name("pager_next").click()
    time.sleep(3)
    # 使用try-except避免未找到元素报错
    try:
        pager_next = driver.find_element_by_class_name("lg-pagination-next")
        driver.execute_script("scroll(0,2500)")  # 下拉滚动条,拉到底,不然会有弹窗影响点击,然后就报错,我这里卡了好久
        driver.find_element_by_class_name("lg-pagination-next").click()
        time.sleep(2)
        get_information()
    except:
        # 没有下一页时则直接关闭
        print("爬取完毕!")
        close()

现在有个问题就是:该在哪里调用下一页函数呢?应该在relay接口那里调用

selennium实战详解_第35张图片

 为什么要在这里调用?因为这个接口切换了句柄。必须要将句柄切换回一级界面之后才能调用翻页函数,否则会找不到翻页的元素,然后报错。

2.2.9构建保存数据函数

这里我们是使用openpyxl库来将数据导出到excel。首先构建initialize_xlsx函数用于初始化excel表。如加入表头,居中对齐,然后再美化一下字体等。对于默认单元格来说,是无法将很多数据显示全的,因此我们还要调宽一下单元格。我们结合代码分析一下:

def initialize_xlsx():
    title = ['职业名称', '公司名称', '公司福利', '薪水范围', '经验要求', '学历要求', '详细地址', '详情信息']
    sheet.append(title)
    # 设置第 1 行的高度
    sheet.row_dimensions[1].height = 30
    # 设置字体样式
    font = Font(name="微软雅黑", size=15, bold=True)
    # 设置对齐样式-水平对齐方式,垂直对齐方式,字体倾斜度,是否换行
    alignment = Alignment(horizontal="center", vertical="center", text_rotation=45, wrap_text=True)
    # 试了一下不能直接通过'A1:F1'来进行批量设置,所以只能一个一个来了
    for i in range(1, len(title) + 1):
        # chr表示将对应Unicode编码数字的字符串表示出来 如 chr(65) = 'A'
        i = chr(64 + i)
        cell = sheet[i + '1']  # 表示从A1开始
        cell.font = font
        # 设置对应列的宽度
        sheet.column_dimensions[i].width = 42
        cell.alignment = alignment
    # 设置保存路径
    workbook.save(filename='C:\\Users\\17591\\Desktop\\lagou.xlsx')

其实我们先说上面的代码是不严谨的,应该要先创建workbook对象,我们在主函数创建。有两种写入方式,第一种就是覆盖式写入,第二种是追加式写入。按自己的喜欢的方式创建。追加式写入的话必须要先创建有excel。我的话我选择了覆盖式写入。在主函数添加如下代码:

    # 追加式写入
    # workbook = load_workbook(filename='C:\\Users\\17591\\Desktop\\lagou.xlsx')
    # print(workbook.sheetnames)
    # sheet = workbook['employment_information']
    # 覆盖式写入
    workbook = openpyxl.Workbook()
    sheet = workbook.active
    # 设置工作表名
    sheet.title = 'employment_information'

 添加完后再调用initialize_xlsx函数

selennium实战详解_第36张图片

构建名为write_xlsx的函数,用于保存一级界面爬取的招聘信息。

def write_xlsx(data):
    # 将数据在excel尾部插入
    sheet.append(data)
    # 将数据保存至目标工作目录。前面我们是使用覆盖式写入的方法,所以工作目录不存在时会新建一个
    workbook.save(filename='C:\\Users\\17591\\Desktop\\lagou.xlsx')

 前面我们已经完成了一级界面的数据爬取与打印,现在我们就将write_xlsx函数添加在打印的下方即可。

selennium实战详解_第37张图片构建名为write_detail的函数用于保存二级界面爬取的数据。

selennium实战详解_第38张图片

 (红框就是我们将要保存的信息。)

可以看到我们要将二级页面的数据加入到一级页面数据的后方。但是sheet.append()的方法是在最近没有写入数据的一行写入数据。就是说无论你第一行有多少列数据,只要一列有数据,那么append就只能添加到下一行,如果第二行也有数据就添加到第三行以此类推。因此我们不能简单的用append()的方法写入数据,要定义一个全局变量的索引,用于确定添加元素的位置,每添加一行索引就加一。有了这思路就好办了,直接上代码

def write_detail(data):
    # 同样要声明全局变量
    global index
    print(index)
    # 二级页面的数据刚好从G列开始保存
    sheet['G' + str(index)] = data[0]
    sheet['H' + str(index)] = data[1]
    index += 1
    workbook.save(filename='C:\\Users\\17591\\Desktop\\lagou.xlsx')

然后在主函数里声明全局变量

selennium实战详解_第39张图片

 同样的,在detail_info函数里调用它

selennium实战详解_第40张图片

 到这里,我们算是完成了本次案例,看一下excel的效果图。

selennium实战详解_第41张图片

 3.完整代码

 虽然呢这里有现成的代码,但我还是建议同学们先理解了之后再查阅。

import time
from selenium.webdriver.common.action_chains import ActionChains
import openpyxl
from openpyxl.styles import Font, Alignment
from selenium import webdriver


def entrance(urls, kws):
    driver.get(urls)
    time.sleep(2)
    # 注意elements 与 element的区别 elements返回的是列表,无法点击
    driver.find_element_by_xpath('//*[@id="changeCityBox"]/ul/li[6]/a').click()  # 切换至深圳站,因为深圳IT岗位较多,新版拉勾切换了没用,所以随便点
    ipt = driver.find_element_by_id("search_input")
    ipt.send_keys(kws)
    time.sleep(2)
    driver.find_element_by_id("search_button").click()
    time.sleep(2)
    get_information()


def get_information():
    # 1.selenium的find_elements_by_xpath不支持直接获取目标属性值
    # 2.因为一页不止一个招聘数据,所以不能用element只能用elements
    # 3.elements里面的元素是selenium标记的元素地址
    # 4.因为elements返回的是列表,而列表没有get_attribute属性,所以要获取所有与目标属性相同节点下的路径,再对其遍历
    name = driver.find_elements_by_xpath('//div[@class="p-top__1F7CL"]/a')
    companyName = driver.find_elements_by_xpath('//div[@class="company-name__2-SjF"]/a')
    welfare = driver.find_elements_by_xpath('//div[@class="il__3lk85"]')
    # 获取该节点的所有文本,其中包括有月薪,学历要求, 工作要求
    info = driver.find_elements_by_xpath('//div[@class="p-bom__JlNur"]')
    salary = driver.find_elements_by_xpath('//div[@class="p-bom__JlNur"]/span')
    # 因为月薪是跟学历/工作要求的父节点在同一父节点下的文本文件,而selenium的.text方法是提取目标路径下的所有文本,包括子节点的文本,所以会有数据重复,要去重
    for i in range(len(name)):  # len(name)表示有多少条招聘信息
        s = info[i].text.replace(salary[i].text, "")
        edu_requirements = s.split(" / ")[0]
        exp_requirements = s.split(" / ")[1]
        print(name[i].text, companyName[i].text, welfare[i].text, salary[i].text, edu_requirements,
              exp_requirements)
        # 将信息写入xlsx表格
        write_xlsx([name[i].text, companyName[i].text, welfare[i].text,
                    salary[i].text, edu_requirements, exp_requirements])
    # 转自访问二级页面接口
    time.sleep(2)
    # 将招聘名称列表传入,用于点击
    relay(name)

    # ————————————————————————————————————————————————————————————————————————————
    # # 鼠标悬停至招聘信息中,获取二级页面简略信息
    # move = driver.find_elements_by_xpath('//div[@class="item__10RTO"]')
    # h = 50
    # for i in move:
    #     print(move.index(i))
    #     # driver.execute_script("arguments[0].scrollIntoView();", i)  # 将滚动条移动到可以显示元素位置
    #     ActionChains(driver).move_to_element(i).perform()
    #     time.sleep(2)
    #     j = driver.find_element_by_xpath('//div[@class="job_info__rB-X6 job_desr_max__N_INq"]')
    #     print(j.text)
    #     time.sleep(2)
    #     # 滑动滚动条,参数可以根据窗体大小适当调整scroll(左边距,上边距)
    #     driver.execute_script("scroll(0,%s)" % str(100 + h))
    #     h += 100


#  定义二级页面访问接口
def relay(name):
    # 句柄确定主界面,即一级界面
    first_handle = driver.current_window_handle
    #  点击访问二级页面
    for i in name:
        # 若没有点击成功,则休眠两秒继续点击
        try:
            i.click()
        except:
            time.sleep(2)
            i.click()
    time.sleep(1)
    all_handles = driver.window_handles
    # 遍历所有句柄进行爬取
    for handle in all_handles:
        if handle != first_handle:
            # 切换句柄
            driver.switch_to.window(handle)
            # 爬取二级页面信息
            detail_info()
            time.sleep(1)
            # 爬取成功后关闭句柄
            driver.close()
    time.sleep(2)
    # 等所有二级界面爬取完后返回一级界面
    driver.switch_to.window(first_handle)
    next_page()


def detail_info():
    print("successfully loading")
    # 获取'//div[@class="work_addr"]'下的文本。跟xpath不同的是selenium递归获取该节点所有文本包括节点下面节点的文本;同一根节点下的所有文本归为一个文本元素
    ad = driver.find_elements_by_xpath('//div[@class="work_addr"]')  # 详细地址
    for i in ad:  # 即使elements里面只有一个元素也不能直接ad.text获取元素文本信息
        address = i.text.strip()[:-5]  # 除去最后的'\n查看地图'

    # 因为职位描述xpath路径不一,所以要判断有可能出现的情况
    information1 = driver.find_elements_by_xpath('//div[@class="job-detail"]')
    information2 = driver.find_elements_by_xpath('//div[@class="job-detail"]/p')
    information = ""
    # 对职位描述进行拼接
    if information1:
        for i in information1:
            information += i.text.strip()
    else:
        for i in information2:
            information += i.text.strip()
    print(address)
    print(information)
    write_detail([address, information])


def next_page():
    # driver.find_element_by_class_name("pager_next").click()
    time.sleep(3)
    # 使用try-except避免未找到元素报错
    try:
        pager_next = driver.find_element_by_class_name("lg-pagination-next")
        driver.execute_script("scroll(0,2500)")  # 下拉滚动条,拉到底,不然会有弹窗影响点击,然后就报错,我这里卡了好久
        driver.find_element_by_class_name("lg-pagination-next").click()
        time.sleep(2)
        get_information()
    except:
        # 没有下一页时则直接关闭
        print("爬取完毕!")
        close()


def write_xlsx(data):
    # 将数据在excel尾部插入
    sheet.append(data)
    # 将数据保存至目标工作目录。前面我们是使用覆盖式写入的方法,所以工作目录不存在时会新建一个
    workbook.save(filename='C:\\Users\\17591\\Desktop\\lagou.xlsx')


def write_detail(data):
    # 同样要声明全局变量
    global index
    print(index)
    # 二级页面的数据刚好从G列开始保存
    sheet['G' + str(index)] = data[0]
    sheet['H' + str(index)] = data[1]
    index += 1
    workbook.save(filename='C:\\Users\\17591\\Desktop\\lagou.xlsx')


def close():
    driver.quit()


def driver_setting():
    # 缩放也要注意格式,不然发生点击事件时可能会报错
    driver.set_window_position(x=500, y=0)
    driver.set_window_size(width=1050, height=900, windowHandle='current')
    # driver.maximize_window()


def initialize_xlsx():
    title = ['职业名称', '公司名称', '公司福利', '薪水范围', '经验要求', '学历要求', '详细地址', '详情信息']
    sheet.append(title)
    # 设置第 1 行的高度
    sheet.row_dimensions[1].height = 30
    # 设置字体样式
    font = Font(name="微软雅黑", size=15, bold=True)
    # 设置对齐样式
    alignment = Alignment(horizontal="center", vertical="center", text_rotation=45, wrap_text=True)
    # 试了一下不能直接通过'A1:F1'来进行批量设置,所以只能一个一个来了
    for i in range(1, len(title) + 1):
        # chr表示将对应Unicode编码数字的字符串表示出来 如 chr(65) = 'A'
        i = chr(64 + i)
        cell = sheet[i + '1']
        cell.font = font
        # 设置对应列的宽度
        sheet.column_dimensions[i].width = 42
        cell.alignment = alignment
    workbook.save(filename='C:\\Users\\17591\\Desktop\\lagou.xlsx')


if __name__ == '__main__':
    url = 'https://www.lagou.com/'
    kw = input('请输入您要想查找的岗位:')
    driver_path = 'C:\edgedriver_win64\msedgedriver.exe'
    # 爬取过程中,移动鼠标可能也会导致爬取失败
    driver = webdriver.Edge(driver_path)
    # 建立第二个driver用于爬取职位详情页面数据,单个driver的话较难实现
    driver_setting()
    # 追加式写入
    # workbook = load_workbook(filename='C:\\Users\\17591\\Desktop\\lagou.xlsx')
    # print(workbook.sheetnames)
    # sheet = workbook['employment_information']
    # 覆盖式写入
    workbook = openpyxl.Workbook()
    sheet = workbook.active
    # 设置工作表名
    sheet.title = 'employment_information'
    # 设置保存详细页面数据的初始位置,使用它必须定义全局变量
    global index
    # 因为第一行是表头,所以从2开始
    index = 2
    initialize_xlsx()
    entrance(url, kw)

4.案例反思

可以说这次案例完成的挺成功的,要是在一些反爬机制较高的网站上获取信息且数据量要求不高的话使用selenium不二之选。使用过程中要注意一些细节问题,也许你的代码没有问题,但可能因为数据没有显示过来你就发生动作,或者说有弹窗在挡住了你的动作目标按钮再者是网页需要你验证才能继续操作等问题,你就要考虑休眠时长和浏览器缩放等原因了,通过本次案例我也能更深刻的领悟selenium的一些特性,提高了我对各个函数定义,使用,衔接,调用的理解与使用能力

你可能感兴趣的:(python,python,selenium)