其他分享
首页 > 其他分享> > scrapy模拟浏览器爬取51job(动态渲染页面爬取)

scrapy模拟浏览器爬取51job(动态渲染页面爬取)

作者:互联网

scrapy模拟浏览器爬取51job

51job链接

网络爬虫时,网页不止有静态页面还有动态页面,动态页面主要由JavaScript动态渲染,网络爬虫经常遇见爬取JavaScript动态渲染的页面。

动态渲染页面爬取,就是模拟浏览器的运行方式,可以做到在浏览器中看到是什么内容爬取的源码就是相应的内容,实现了可见即可爬。

这个方法在爬虫过程中会打开一个浏览器 加载该网页,自动操作浏览器浏览各个网页,同时也可爬取加载的页面 HTML。用一句简单 而通俗的话说,就是使用浏览器渲染方法将爬取动态网页变成爬取静态网页。

我们可以用 Python 的 Selenium 库模拟浏览器完成抓取。Selenium 是一个用于 Web 应 用程序测试的工具。Selenium 测试直接运行在浏览器中,浏览器自动按照脚本代码做出单 击、输入、打开、验证等操作,就像真正的用户在操作一样

安装部署

Selenium是一个 自动化测试工具,利用它可以驱动浏览器执行特定的动作,如点击、下拉等操作, 同时还可以获取浏览器当前呈现的页面的源代码,做到可见即可爬。

Selenium库安装如下:

pip install selenium

Selenium库安装后,可在命令行下进行测试,具体测试指令如下:

import selenium

输入以上内容,没有出现错误,说明Selenium库安装成功,具体如下图。
在这里插入图片描述
浏览器驱动的下载安装

浏览器驱动也是一个独立的程序,是由浏览器厂商提供的,不同的浏览器需要不同的浏 览器驱动。 比如 Chrome 浏览器和火狐浏览器有各自不同的驱动程序。

浏览器驱动接收到我们的自动化程序发送的界面操作请求后,会转发请求给浏览器,让 浏览器去执行对应的自动化操作。浏览器执行完操作后,会将自动化的结果返回给浏览器驱 动,浏览器驱动再通过 HTTP 响应的消息返回给我们的自动化程序的客户端库。自动化程序 的客户端库接收到响应后,将结果转化为数据对象返回给程序代码。

在下载 Chrome 浏览器驱动前,首先确定 Chrome 浏览器的版本。点击 Chrome 浏览器“自 定义及控制 Goole Chrome”按钮,选择“帮助”、“关于 Google Chrome(G)”,查看浏览 器的实际版本号。
在这里插入图片描述
https://chromedriver.storage.googleapis.com/index.html 是 Chorome 浏览器驱动 的下载地址。按照 Chrome 的版本号以及操作系统,选择不同的版本下载
在这里插入图片描述
下载完成后解压缩,将 ChromeDriver.exe 拷贝到指定目录,后续编写代码要指定驱动所在目录。

声明浏览器对象

Selenium支持多个浏览器,比如:Chrome、Firefox、Edge等,还可以支持Android、BlackBerry等手机段浏览器。另外也支持无界面浏览器PhantomJS。

具体的初始化方式如下:

from selenium import webdriver
browser = webdriver.Chrome(executable_path=path)
browser=webdriver.Firefox(executable_path=path)
browser=webdriver.Edge(executable_path=path)
browser=webdriver.PhantomJS(executable_path=path)
browser=webdriver.Safari(executable_path=path)

其中,executable_path表示:浏览器驱动器存放位置。
以上步骤实现了浏览器对象的初始化,并将其赋值为browser对象。

访问页面

Selenium使用get()方法请求网页,具体的语法如下:

browser.get(url)

访问页面的实现方式如下:

from selenium import webdriver
path="E:/chromedriver.exe"
browser = webdriver.Chrome(executable_path=path) #获取 Chrome 驱动实例
browser.get('https://www.taobao.com/')#打开淘宝
print(browser.page_source)  #返回源码
browser.close() #关闭浏览器

运行程序后,弹出了Chrome浏览器并且自动访问了淘宝,然后输出淘宝网页的源代码,最后关闭浏览器。

Webdriver.Chrome()为获取 Chrome 浏览器驱动实例,Webdriver 后的方法名是浏览器 的名称,如 webdriver.Firefox()为火狐浏览器的驱动实例。其中参数 d:\ChromeDriver.exe 为驱动所在的路径。参数可省略,但是需要将 ChromeDriver.exe 的路径放入到系统的环境 变量中。wd.get(url)可以打开指定的网页。wd.close()关闭 selenium 打开的浏览器。

在 selenium 模块的使用过程中,常见错误如下

  1. 错误信息为:"Exception AttributeError:Service object has no attribute process in…”, 可能是 geckodriver 环境变量有问题,重新将 webdriver 所在目录配置 到环境变量中。或者直接在代码中指定路径:webdriver.Chrome(‘ChromeDriver 全路径’)
  2. 错 误 信 息 为 : selenium.common.exceptions.WebDriverException: Message: Unsupported Marionette protocol version 2,required
  3. 可能是 Chrome 版本太低。

元素选择器

要想对页面进行操作,首先要做的是选中页面元素。元素选取方法如下表
在这里插入图片描述在这里插入图片描述
从命名上来讲,定位一个元素使用的单词为 element,定位多个元素使用的单词为 elements。从使用的角度来说,定位一个元素,返回的类型为元素对象,如果存在多个匹配 第一个,如果查找不到匹配的元素,会出现异常,程序代码不会继续执行;定位多个元素返 回的数据类型为列表,可循环遍历、可使用列表索引,查找不到匹配元素不会出现异常,适合于复杂情况下的判断。

以下以百度首页为例进行基本案例讲解。CSS 选择器的基本使用方法要求读者务必掌 握,简要回顾下。Id 选择器使用#,如“#u1”,定位 id 为 u1 的元素;类选择器使用“.”, 如“.mnav”,定位所有 class 为 mnav 的元素;元素选择器直接使用标签名,如“div”,定 位所有的 div;组合选择器,以上多种元素选择方式组合在一起,是使用频率最高的一类选 择器。如“#u1 .pf”,定位 id 为 u1 的元素下的所有 class 为 pf 的元素;“#u1>.pf”,定 位 id 为 u1 的元素下的 class 为 pf 的元素,并且要求 class 为 pf 的元素是 u1 的直接子级。

在这里插入图片描述
为了更好体现定位到指定元素,使用了 get_attribute 方法来获 取元素的属性,参数可以是合法的 html 标签属性,如 class 或 name,outerHTML 表示获取定 位元素的 html 并且包括元素本身。element1.text 表示获取元素的文本节点,并包括下级 文本。

下表罗列出常用的 CSS 选择器和其他选择器对比。
在这里插入图片描述

操纵元素的方法

操控元素通常包括点击元素、在输入框中输入字符串、获取元素包含的信息。

Selenium可驱动浏览器执行一些操作,即可以让浏览器模拟执行一些动作,常见的操作及方法如下:
输入文字:使用send_keys()方法实现
清空文字:使用clear()方法实现
点击按钮:使用click()方法实现

from selenium import webdriver
import time
path="E:/chromedriver.exe"
browser = webdriver.Chrome(executable_path=path)
browser.get('https://music.163.com/')
#获取输入框
input = browser.find_element_by_id('srch')
#搜索框输入Andy Lao,但是未点击搜索按钮所以不进行搜索
input.send_keys('Andy Lao')
time.sleep(1)
#清空输入框
input.clear()
input.send_keys('刘德华')
#获取搜索按钮
button = browser.find_element_by_name('srch')
#点击按钮完成搜索任务
button.click()
#关闭浏览器
browser.close()

程序实现流程如下:
1.驱动浏览器打开网易云音乐;
2.使用find_element_by_id()方式获取输入框;
3.使用send_keys()方法输入:Andy Lao;
4.等待一秒后使用clear()清空输入框;
5.再次调用send_keys()方法输入:刘德华;
6.再次使用使用find_element_by_id()方式获取输入框;
7.调用click()方法完成搜索动作。

动作链

Selenium可驱动浏览器执行其他操作,这些操作没有特定的执行对象,比如:鼠标拖拽、键盘按键等,此类操作称为动作链。

Selenium库提供了Actionchains模块,该模块专门处理动作链,比如:鼠标移动,鼠标按钮操作,按键、上下文菜单(鼠标右键)交互等。

click(on_element=None) ——单击鼠标左键
click_and_hold(on_element=None) ——点击鼠标左键,不松开
context_click(on_element=None) ——点击鼠标右键
double_click(on_element=None) ——双击鼠标左键
drag_and_drop(source, target) ——拖拽到某个元素然后松开
drag_and_drop_by_offset(source, xoffset, yoffset) ——拖拽到某个坐标然后松开
key_down(value, element=None) ——按下某个键盘上的键
key_up(value, element=None) ——松开某个键move_by_offset(xoffset, yoffset) ——鼠标从当前位置移动到某个坐标move_to_element(to_element) ——鼠标移动到某个元素move_to_element_with_offset(to_element, xoffset, yoffset) ——移动到距某个元素(左上角坐标)多少距离的位置
perform() ——执行链中的所有动作
release(on_element=None) ——在某个元素位置松开鼠标左键
send_keys(*keys_to_send) ——发送某个键到当前焦点的元素
send_keys_to_element(element, *keys_to_send) ——发送某个键到指定元素

进入正题!!!

我使用的是火狐浏览器,你们可以自行决定

当我们爬取的时候,会遇到有滑块,这个时候我们就需要知道滑块到底滑行了多少,在模拟人的操作时,前面一个阶段,我们会快速拉滑块,给他一个正的加速度,在滑块要到的时候,我们就要降低速度。

def get_track(distance,t):
    track = []
    current = 0  #当前初始位置
    #mid = distance * t / (t+1)
    mid = distance * 3 / 4  
    #print(mid)
    v = 6.8  # 初速度
    while current < distance:
        if current < mid:
            a = 2
        else:
            a = -3
        v0 = v  
        v = v0 + a * t
        move = v0 * t + 1/2 * a * t * t  #计算滑行的距离,与高中的物理知识相关,不知道的了解一下哟
        current += move
        #print(current)
        track.append(round(move))
    return track

而滑行的速度该如何计算?举一个例子
在这里插入图片描述
使用开发者工具,或者ps,可以找出圆圈的长和宽,假设为:40x30,接着我们可以找出整个滑块的长和宽,假设为:340x30,则滑块需要滑行的距离为(340-40),也就是300。所以我们在使用小程序计算的时候,总的距离大概是300左右,不要超过太多和少太多。

对滑块的操作:
①点击鼠标左键,不松开

ActionChains(self.browser).click_and_hold(slider).perform() 

②向右拖

ActionChains(self.browser).move_by_offset(x, 0).perform()  #向右滑动

③松开鼠标

ActionChains(self.browser).release().perform()  #释放操作

完整代码:

Middleware.py

# -*- coding: utf-8 -*-

# Define here the models for your spider middleware
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/spider-middleware.html

from scrapy import signals
from selenium.common.exceptions import TimeoutException
from selenium.webdriver import Firefox
from selenium.webdriver.firefox.options import Options
import time,requests,random
from selenium.webdriver.common.action_chains import ActionChains 
from scrapy.http import HtmlResponse

def get_track(distance,t):
    track = []
    current = 0
    #mid = distance * t / (t+1)
    mid = distance * 3 / 4
    #print(mid)
    v = 6.8
    while current < distance:
        if current < mid:
            a = 2
        else:
            a = -3
        v0 = v
        v = v0 + a * t
        move = v0 * t + 1/2 * a * t * t
        current += move
        #print(current)
        track.append(round(move))
    return track

class SeleniumMiddleware:
    
    def __init__(self):
        # 1.创建chrome参数
        opt= Options()
        
        # 2.创建无界面对象
        self.browser = Firefox(executable_path='D:\geckodriver.exe', options=opt)   # 创建无界面对象
        self.browser.maximize_window() ##浏览器最大化

    @classmethod
    def from_crawler(cls, crawler):  # 关闭浏览器
        s = cls()
        crawler.signals.connect(s.spider_closed, signal=signals.spider_closed)
        return s
        
    ##按照轨迹拖动,完全验证
    def move_to_gap(self,slider,tracks):
#   #     拖动滑块到缺口处 param slider: 滑块,param track: 轨迹
        ActionChains(self.browser).click_and_hold(slider).perform() 
        print(tracks)
        for x in tracks:
            print(x)
            ActionChains(self.browser).move_by_offset(x, 0).perform()  #向右滑动
        ActionChains(self.browser).release().perform()  #释放操作
        # # perform() ——执行链中的所有动作 ,release(on_element=None) ——在某个元素位置松开鼠标左键
        
    def process_request(self, request, spider):
        ## # 判断是否需要模拟器下载, 如果不需要模拟直接跳过模拟去download下载
        try:
            ## 3.打开指定的网页
            self.browser.get(request.url)  
            
            #滑块处理
            if request.url.find("https://jobs.51job.com/")!= -1:
                try:
                    yzm = self.browser.find_element_by_xpath("//span[@id='nc_1_n1z']")
                    print(yzm)
                    if yzm:
                        print("====有滑块=====")
                        self.move_to_gap(yzm,get_track(258, 2))  # 拖住滑块
                        time.sleep(10)
                        print("====lllllll====")
                    else:
                        print("===没有滑块===")
                except Exception as e:
                    print("==="+str(e))
            else:
                print("===feeder====")
                time.sleep(2)
            return HtmlResponse(url=request.url, body=self.browser.page_source, request=request, encoding='utf-8',status=200)
        except TimeoutException:
            return HtmlResponse(url=request.url, status=500, request=request)
        
    def spider_closed(self):
        self.browser.quit()

job.py

# -*- coding: utf-8 -*-
import scrapy
#from scrapy.utils.response import open_in_browser
import copy

class JobSpider(scrapy.Spider):
    name = 'job'
    allowed_domains = ['51job.com']
    start_urls=['https://search.51job.com/list/060000,000000,0000,00,9,99,%25E5%25A4%25A7%25E6%2595%25B0%25E6%258D%25AE,2,{i}.html' for i in range(1,2)]
   
    def start_requests(self):
        for url in self.start_urls:
            yield scrapy.Request(url)
        #yield scrapy.Request("https://httpbin.org/ip")
    def parse(self, response):
        item = {}
        print("======")
        print(len(response.xpath("//div[@class='j_joblist']/div[@class='e']")))
        for entry in response.xpath("//div[@class='j_joblist']/div[@class='e']"):
            url = entry.xpath(".//p[@class='t']/../@href").get()
            item['url'] = url
            item['job']=entry.xpath(".//p[@class='t']/span[1]/text()").get()
            item['price'] = entry.xpath(".//span[@class='sal']/text()").get()
            item['where'] = entry.xpath(".//p[@class='info']/span[2]/text()").get().split('  |  ')[0]
            item['jingyan'] = entry.xpath(".//p[@class='info']/span[2]/text()").get().split('  |  ')[1]
            item['xueli'] = entry.xpath(".//p[@class='info']/span[2]/text()").get().split('  |  ')[2]
            item['gongsi']=entry.xpath(".//div[@class='er']/a/text()").get()
            item['daiyu']=entry.xpath(".//p[@class='tags']/@title").get()
            yield scrapy.Request(url,callback=self.parse_detail,meta={'item':copy.deepcopy(item)},dont_filter=True)
    def parse_detail(self,response):
        item = response.meta['item']
        content = response.xpath("//div[contains(@class,'job_msg')]").xpath("substring-before(.,'职能类别:')").xpath('string(.)').extract()
        desc=""
        for i in content:
            desc=desc.join(i.split())
        item['desc']=desc
        yield item

将数据存进mongodb

不知如何操作的话,可以看看之前我的文章

# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
#import pymysql

class Job51Pipeline(object):
    def process_item(self, item, spider):
        return item
       
import pymongo
from urllib import parse

class NewPipeline_mongo:
    def __init__(self, mongo_uri, mongo_db,account,passwd):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db
        self.account = account
        self.passwd = passwd
        
    @classmethod
    def from_crawler(cls, crawler):
        #print(crawler.settings.get('USERNAME'))
        return cls(
            mongo_uri=crawler.settings.get('MONGO_URI','localhost'),
            mongo_db=crawler.settings.get('MONGO_DB','cq'),
            account = crawler.settings.get('USERNAME','root'),
            passwd = crawler.settings.get('PWD','123456')
        )

    def open_spider(self, spider):
        uri = 'mongodb://%s:%s@%s:27017/?authSource=admin' % (self.account, parse.quote_plus(self.passwd),self.mongo_uri)
        #print(uri)
        self.client = pymongo.MongoClient(uri)
        self.db = self.client[self.mongo_db]
        print(self.mongo_db)
        
    def process_item(self, item, spider):
        print(item)
        collection = 'job51'
        self.db[collection].insert_one(dict(item))
        return item
    
    def close_spider(self, spider):
        self.client.close()

settings.py

ROBOTSTXT_OBEY = False

# Configure maximum concurrent requests performed by Scrapy (default: 16)
CONCURRENT_REQUESTS = 1

# Configure a delay for requests for the same website (default: 0)
# See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
DOWNLOAD_DELAY = 1
DOWNLOADER_MIDDLEWARES = {
    'job51.middlewares.SeleniumMiddleware': 543,
}

# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
    'job51.pipelines.NewPipeline_mongo': 200,
}

在这里插入图片描述

标签:浏览器,get,51job,class,爬取,item,scrapy,self,browser
来源: https://blog.csdn.net/weixin_45609831/article/details/120234892