爬虫概述论

目录

一:windows环境下进入虚拟环境

  • 1: 切换D盘路径:d:
  • 2:切换到Script路径下:
  • 3: 在这个路径下使用: activate.bat

二: requests模块的基本使用

2.1: requests模块请求对象:

  • 1: 发送请求获取响应:response = requests.get(‘url地址’)

  • 2:发送带headers的请求:response = requests.get(url, headers=headers)

    headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"}
    
  • 3: 发送带参数的请求:requests.get(url,headers=headers, params=kw)

    kw = {'wd':'长城'}
    

2.2: requests模块的响应对象:

  • 1:获取响应的内容: response.text

  • 2:获取响应的内容: response.content.decode(‘utf8’)

  • 3: response.content 与 response.text的区别?

    """
    response.text返回的是字符串,编码方式不确定。
    response.content返回的是字节
    两个的修改编码方式不同: response.encoding = 'gbk'
    response.content.decode('utf8')
    """
    
  • 4: 获取响应的url : response.url

  • 5:获取响应的状态码: resposne.status_code

  • 6: 获取请求的头: response.request.headers

  • 7: 获取响应头: response.headers

  • 8: 获取请求的cookie信息: response.request._cookies

  • 9: 获取响应的cookie信息: response.cookies

  • 10:将响应的json转换成python对象(dict或者list)response.json()

2.3: requests模块发送post请求:

  • 1:发送post请求:

    data = {
        'f': 'auto', # 表示被翻译的语言是自动识别
        't': 'auto', # 表示翻译后的语言是自动识别
        'w': '人生苦短' # 要翻译的中文字符串
    }
    response = requests.post("http://www.baidu.com/", data = data,headers=headers)
    
    

2.4:requests模块使用代理ip:

  • 1: 代理ip的使用:

    proxies = { 
        "http": "http://12.34.56.79:9527", 
        "https": "https://12.34.56.79:9527", 
    }
    response = requests.get(url, proxies=proxies)
    

2.5: requests模块处理Cookie:

  • 1: 创建Session对象: session = requests.session()

  • 2: 利用Session对象发送请求:

    response = session.get(url, headers, ...)
    response = session.post(url, data, ...)
    
  • 3: 服务器将cookie封装在resposne.cookies属性中,该属性是个cookieJar类型的数据。

    from requests.utils import dict_from_cookiejar,cookiejar_from_jar
    # 将cookiejar转换成字典类型
    cookies_dict = requests.utils.dict_from_cookiejar(response.cookies)
    # 将字典类型转换成cookiejar类型
    cookies_jar = requests.utils.cookiejar_from_jar(cookies_dict)
    

2.6: 忽略CA证书的认证

  • # verify=False 取消ssl安全认证证书,直接登录
    resp = requests.get(url, verify=False)
    

2.7: 超时参数设置:

  • response = requests.get(url, timeout=3)
    

2.8:刷新重试:

  • 安装超时重试模块:pip install retrying

  • #利用retry装饰器,被装饰的函数如果发送超时异常,就会被重新执行
    # stop_max_attempt_number=3 最多尝试次数为3
    @retry(stop_max_attempt_number=3)
    def timeount_func():
        # 1.构建url地址
        url = "http://www.itcast.cn"
        print("-" * 40)
        # 2.发送get请求--要求0.03秒给出效应,否则超时
        resp = requests.get(url, timeout=0.03)
            # 3.返回响应
        return resp
    

三: 数据的提取

3.1:了解返回的数据类型:

  • 1:返回json字符串:jsonpath提取数据
  • 2: 返回xml字符串: BeautifulSoup , Xpath语法(lxml)
  • 3: 返回html字符串: BeautifulSoup , Xpath语法(lxml)

3.2:json与python类型的转换:

  • json转python : my_python= json.loads(my_json)
  • python转json: my_json = json.dumps(my_python, ensure_ascii=False, indent=4)

3.3: 使用jsonpath提取数据:

  • 安装jsonpath模块: pip install jsonpath

  • from jsonpath import jsonpath
    # 取到就返回一个列表,得不到返回Flase
    ret = jsonpath(a, 'jsonpath语法规则字符串')
    
  • jsonpath的语法规则:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7fRS5x53-1613550120295)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\image-20210107161650393.png)]

3.4: 使用Beautifulsoup提取数据:

  • 1:提取xml或者html数据。

  • 2:安装:pip install beautifulsoup4

  • 3:基本使用:

    # 1.导入模块
    from bs4 import BeautifulSoup
    
    # 2.准备html数据
    html = """ html数据"""
    
    # 3.创建BeautifulSoup类的对象
    soup = BeautifulSoup(html)
    
    # 4.格式化输出soup对象的内容
    print soup.prettify()
    
  • 4: soup的其他属性:

    # 1: 获取标签元素: 例如获取标题标签
    soup.title
    
    # 2:获取标签元素的内容:
    soup.title.get_text()
     
    # 3:获取属性的值
    soup.a.get('href')
    
  • 5: find()与find_all()方法:

    # 区别: find返回符合条件的第一个元素,find_all返回符合条件的所有元素列表
    a_list = soup.find_all(name='a')
    a = soup.find(name='a')
    a_list = soup.find_all(name= ['a', 'b', 'c'])
    
    # 都可以使用正则表达式:name以a开头的元素。
    a = soup.find(name = re.compile("^a"))
    a_list = soup.find_all(name = re.compile("^a"))
    
    #都可以通过属性查找元素:name属性是a的元素
    a =  soup.find(attrs = {'name': 'a'})
    a_list = soup.find_all(attrs = {'name': 'a'})
    
    # 通过文本查找元素:查询文本是a的所有的元素
    a = soup.find(text = 'a')
    a_list = soup.find(text = 'a')
    
    # 通过关键字查询:
    # 注意:由于class是python定义类的关机字,所以用class_代替,作为区分。
    soup.find_all(class_="sister")
    soup.find_all(id='link2')
    
  • 6: select元素查找:

    # 1: 类选择器:
    soup.select('.sister')
    
    # 2: id选择器:
    soup.select('#link1')
    
    # 3: 标签选择器:
    soup.select('标签名称')
    
    # 4: 属性选择器:
    soup.select('a[class="sister"]')
    
    # 5:层级选择器:
    soup.select('p #link1')
    

3.5:使用Xpath语法提取数据:

  • 1: 用于提取HTML或者XML文件中的数据。

  • 2:常用的Xpath语法:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yJ06xiCZ-1613550120300)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\image-20210107181101238.png)]

  • 3:查询特定的节点:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Oq0OBz0-1613550120303)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\image-20210107181239398.png)]

  • 4: 通配符的使用:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FqRvCyb5-1613550120306)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\image-20210107181418434.png)]

  • 5: 选取多个路径:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mytq2Oj6-1613550120308)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\image-20210107181441709.png)]

3.6:使用lxml提取数据:

  • 1:安装: pip install lxml

  • 2:lxml的基本使用:

    # 1:导入etree库,然后使用HTML方法得到能够使用xpath语法的Element对象。
    from lxml import html
    etree = html.etree
    
    # 2: 得到html字符串
    text = """ HTML字符串 """
    
    # 3:得到 Element对象 
    html = etree.HTML(text) 
    
    # 4: Element对象使用xpath语法获取元素列表:
    href_list = html.xpath("xpath语法")
    
  • 3:先分组再提取的思想。

四: 分布式爬虫:

4.1: 线程进程队列的基本使用:

  • 1: 进程的基本使用:

    1:导入进程模块
    import multiprocess
    
    2:创建子进程
    p1 = multiprocess.Process(target=sing)
    
    3:启动子进程
    p1.start()
    
    4:获取进程对象
    multiprocessing.current_process()
    
    5:获取进程ID
    os.getpid()
    
    6:获取父进程ID
    os.getppid()
    
    7:杀死进程
    os.kill(os.getpid(), 9)
    
    8:进程带有参数
    
    元组传参
    p1 = multiprocessing.Process(target=sing, args=(6))
    
    字典传参
    注意:kwargs中键必须和参数名相同
    p2 = multiprocessing.Process(target=dance, kwargs={"num": 4})
    
    9:设置守护线程
    
    子进程对象名.daemon = True
    
    子进程自杀:
    子进程对象名.tarminate()
    
  • 2:线程的基本使用:

    1:导线程包
    import threading
    
    2:创建子线程
    t1 = threading.Thread(target=sing)
    
    3:启动子线程
    t1.start()
    
    4:获取线程对象
    threading.current_thread()
    
    5:线程的传参方式:
    
    t1 = threading.Thread(target=sing, args=(3,))
    t2 = threading.Thread(target=sing, kwargs={"num": 3})
    
    6:创建守护线程:
    方案一:
    t1 = threading.Thread(target=sing,daemon=True)
    方案二:
    t1.setDaemon(True)
    
    方案三:
    t1.daemon = True
    
    7:设置线程同步
    t1.join()
    
    8:锁的使用
    
    创建锁:
    mutex = threading.Lock()
    
    上锁:
    mutex.acquire()
    
    解锁:
    mutex.release()
    
  • 3:进程与线程的区别?

    - 关系对比:
      - 线程依附进程,有进程才有线程
      - 一个进程默认有一个线程,也可以有多个线程
    - 区别对比:
      - 全局变量:
        - 进程不能共享全局变量
        - 线程可以共享全局变量,出现资源竞争问题,可以通过互斥锁和线程同步解决。
      - 开销上:
        - 创建进程的开销比创建线程的开销大
      - 概念上:
        - 进程是操作系统资源分配的单位
        - 线程是cpu调度的单位
      - 关系上:
        - 线程依附进程存在,不能单独存在
      - 稳定性上
        - 多进程编程比单进程多线程编程稳定性更好
    - 优缺点对比:
      - 进程:优点(稳定性,可以使用多核)缺点(开销大)
      - 线程:优点(开销小)缺点(不能使用多核)
    
  • 4: 队列模块的使用:

    # put的时候计数+1,get不会-1,get需要和task_done 一起使用才会-1
    
    from queue import Queue
    # 1:构建队列
    # maxsize=100 最大容量100
    q = Queue(maxsize=100)
    
    item = {}
    # 2:把数据放入到队列中,如果队列数据已满,就进行等待直到队列不满时把数据添加到队列中
    q.put(item)
    # 把数据放入到队列中,如果队列数据已满,就不进行等待,引发异常
    q.put_nowait(item)  
    
    # 3:把数据从队列中取出,如果队列为空,就进行等待直到队列不为空,返回数据
    q.get()
    # 把数据从队列中取出,如果队列为空,就不进行等待,引发异常
    q.get_nowait()
    
    # 获取队列中元素个数
    q.qsize()
    # 获取未完成的任务数
    q.unfinished_tasks
    
    # 让 unfinished_tasks 属性 -1
    q.task_done()
    
    # 当前程序进行等待状态,直到队列的 unfinished_tasks 属性为 0 时继续执行
    q.join()
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T34eaMSy-1613550120309)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\image-20210107185256421.png)]

4.2:多线程爬虫:

  • 1: 多线程爬虫的基本思想:生产者消费者模式。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eJs6QcVS-1613550120310)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\image-20210107185403473.png)]

  • 2: 糗事百科多线程爬虫:

    import time
    from queue import Queue
    from threading import Thread
    
    import requests
    from lxml import html
    etree = html.etree
    
    class QiubaiSpider(object):
    
        def __init__(self):
            self.url = 'http://www.qiushibaike.com/8hr/page/{}/'
            self.headers = {
                "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36",
            }
            self.url_queue = Queue() # url队列
            self.html_queue = Queue() # html队列
            self.result_queue = Queue() # 结果队列
    
        def get_urls(self): # 生产url,将url放入url队列中
            urls = [self.url.format(page) for page in range(1, 14)]
            for url in urls:
                self.url_queue.put(url)
    
    
        def send_request(self): # 发送url请求,获取响应,提取html加入html队列
            while True:
                url = self.url_queue.get()
                response = requests.get(url, headers = self.headers)
                html_str = response.content.decode()
                self.html_queue.put(html_str)
                self.url_queue.task_done() # 让url队列-1
    
        def parser_response(self):
            while True:
                html_str = self.html_queue.get()
                eroot = etree.HTML(html_str) # 构造 Element对象
                title_list = eroot.xpath("//a[@class='recmd-content']/text()")
                self.result_queue.put(title_list)
                self.html_queue.task_done()
    
        def save(self): # 打印标题
            while True:
                title_list = self.result_queue.get()
                print(title_list)
                self.result_queue.task_done()
    
        def run(self):
            thread_list = [] # 所有线程存储在列表中
    
            url_thread = Thread(target=self.get_urls)
            thread_list.append(url_thread) # 生成url线程加入列表,没有执行
    
            html_thread = Thread(target=self.send_request)
            thread_list.append(html_thread) # 消费url获取响应线程加入列表
    
            parser_thread = Thread(target=self.parser_response)
            thread_list.append(parser_thread) # 解析响应线程加入列表
    
            save_thread = Thread(target=self.save)
            thread_list.append(save_thread) # 保存数据线程加入列表
    
            # 给子线程设置守护线程,并启动子线程
            for thread in thread_list:
                thread.setDaemon(True)
                thread.start()
            # 注意:主线程设置延迟,防止主线程退出
            time.sleep(3)
    
            for queue in [self.url_queue, self.html_queue, self.result_queue]:
                # 三个队列,分别阻塞主线程退出,直到所有的任务全部完成,unfinished_task == 0 取消阻塞
                queue.join()
                # 取消阻塞,代码执行带114行,主线程退出,子线程也就跟着结束了,整个爬虫结束
    
    if __name__ == '__main__':
        spider = QiubaiSpider()
        spider.run()
    
  • 3:代码中time.sleep(3)的作用:

    """
    子线程的启动需要花费时间,如果主线程不睡眠一段时间,执行下面的代码 queue.join()的时候,可能子线程还没有启动呢,根本不可能阻塞,导致主线程直接退出。
    """
    
  • 4: 为什么要用子线程阻塞主线程?

    """
    因为子线程设置了守护线程,也就是主线程退出会导致线程也退出,而主线程执行到最后的时候,可能子线程还没有执行完,导致爬取一半程序退出。
    """
    

4.3: 多进程爬虫:

  • 1:多进程模块是: multiprocessing模块。
  • 2: 队列改用JoinableQueue ,不再使用Queue队列的原因是,进程间无法共享一个变量。底层是进程间的通信。
  • 3:设置守护进程的方式: task.daemon = True
  • 4:上面代码只需要修改进程和队列其他的不变。

五: selenium模块:

  • 1:安装: pip install selenium

  • 2: selenium的基本使用:

    from selenium import webdriver
    
    # 1.创建浏览器对象
    # 如果driver添加了环境变量则不需要设置executable_path
    driver = webdriver.Chrome()
    
    # 2.向目标URL发送请求
    driver.get("http://www.baidu.com/")
    
    # 3.操作浏览器对象的行为
    # 把网页保存为图片,69版本以上的谷歌浏览器将无法使用截图功能
    driver.save_screenshot("baidu.png")
    # 打印页面的标题
    print(driver.title)
    
    # 4.退出模拟浏览器 不退出会有残留进程!
    driver.quit()
    
  • 3: driver对象的常用属性和方法:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g1tDUPhp-1613550120312)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\image-20210107200857401.png)]

  • 4:定位标签元素,获取元素对象:

    find_element_by_id                         (返回一个元素)
    find_element(s)_by_class_name             (根据类名获取元素列表)
    find_element(s)_by_name                 (根据标签的name属性值返回包含标签对象元素的列表)
    find_element(s)_by_xpath                 (返回一个包含元素的列表)
    find_element(s)_by_link_text             (根据连接文本获取元素列表)
    find_element(s)_by_partial_link_text     (根据链接包含的文本获取元素列表)
    find_element(s)_by_tag_name             (根据标签名获取元素列表)
    find_element(s)_by_css_selector         (根据css选择器来获取元素列表)
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Va590k2W-1613550120318)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\image-20210107201116514.png)]

  • 5:元素操作:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fMFSjQMH-1613550120319)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\image-20210107201214694.png)]

  • 6: 标签页的切换:

    # 1. 获取当前所有的标签页的句柄构成的列表
    current_windows = driver.window_handles
    
    # 2. 根据标签页句柄列表索引下标进行切换
    driver.switch_to.window(current_windows[0])
    
    # 切换到frame内部:
    login_frame = driver.find_element_by_id('login_frame') # 根据id定位 frame元素
    driver.switch_to.frame(login_frame) # 转向到该frame中
    
    # 切换出frame
    windows = driver.window_handles
    driver.switch_to.window(windows[0])
    
  • 7: 处理cookie:

    # 获取当前标签页的全部cookie信息
    print(driver.get_cookies())
    # 把cookie转化为字典
    cookies_dict = {cookie[‘name’]: cookie[‘value’] for cookie in driver.get_cookies()}
    
    #删除一条cookie
    driver.delete_cookie("CookieName")
    
    # 删除所有的cookie
    driver.delete_all_cookies()
    
  • 8: 执行js代码:

    js = 'window.scrollTo(0,document.body.scrollHeight)' # js语句
    driver.execute_script(js) # 执行js的方法
    
  • 9: 显示等待与隐式等待:

    # 隐式等待:获取元素之前设置
    driver.implicitly_wait(10) # 隐式等待,最长等20秒  
    
    # 显式等待,获取元素之前设置。
    WebDriverWait(driver, 20, 0.5).until(
        EC.presence_of_element_located((By.LINK_TEXT, '好123')))  
    # 参数20表示最长等待20秒
    # 参数0.5表示0.5秒检查一次规定的标签是否存在
    # EC.presence_of_element_located((By.LINK_TEXT, '好123')) 表示通过链接文本内容定位标签
    # 每0.5秒一次检查,通过链接文本内容定位标签是否存在,如果存在就向下继续执行;如果不存在,直到20秒上限就抛出异常
    
  • 10: 手动实现页面加载:

    找到就退出,找不到就向下滑动:

    # 让浏览器执行js往下拖拽
    for i in range(1, 10):
        time.sleep(1)
        try:
            # 报错:no such element
            # 原因:根据当前的xpath无法提取到元素,因为页面还未加载处理啊
            # 解决方案:往下拖拽直到元素找到
            element = driver.find_element_by_xpath('//span[@class="subtitle ml6"]')
            print(element.text)
            break
        except:
              # 有异常代表元素没有找到,往下滚动500,继续找
            js = "window.scrollTo(0, {})".format(500 * i)
            driver.execute_script(js)
    
    
  • 11: 无界面模式:

    from selenium import webdriver
    
    options = webdriver.ChromeOptions() # 创建一个配置对象
    options.add_argument("--headless") # 开启无界面模式
    options.add_argument("--disable-gpu") # 禁用gpu
    
    # options.set_headles() # 无界面模式的另外一种开启方式
    driver = webdriver.Chrome(chrome_options=options) # 实例化带有配置的driver对象
    
    driver.get('http://www.itcast.cn')
    print(driver.title)
    driver.quit()
    
  • 12 : 代理IP

    from selenium import webdriver
    
    options = webdriver.ChromeOptions() # 创建一个配置对象
    options.add_argument('--proxy-server=http://202.20.16.82:9527') # 使用代理ip
    
    driver = webdriver.Chrome(chrome_options=options) # 实例化带有配置的driver对象
    
  • 13:替换User-agent: 谷歌的驱动默认是谷歌的User-agent

    from selenium import webdriver
    
    options = webdriver.ChromeOptions() # 创建一个配置对象
    options.add_argument('--user-agent=Mozilla/5.0 HAHA') # 替换User-Agent
    
    driver = webdriver.Chrome('./chromedriver', chrome_options=options)
    

六:反爬与反反爬:

6.1:身份识别反爬

  • 通过headers中的User-Agent字段来反爬

    """
    反爬的原理: 默认是没有User-Agent的。
    """
    
    解决方案: 
    添加User-Agent
    构造User-Agent池
    
  • 通过referer字段或者是其他字段来反爬

    """
    爬虫默认情况下不会带上referer字段,服务器端通过判断请求发起的源头,以此判断请求是否合法
    """
    解决方案: 
    增加referer字段
    
  • 通过cookie来反爬

    """
    通过检查cookies来查看发起请求的用户是否具备相应权限,以此来进行反爬
    """
    解决方案:
    进行模拟登陆,成功获取cookies之后在进行数据爬取
    
  • 通过从html静态文件中获取请求数据(github)

    """
    反爬原理: 在登录页面我们可以增加一个隐藏框,存储请求token,点击登录的时候写带我们的token访问后端,后端判断token是否存在,或者是否正确,来判断是不是爬虫。
    """
    解决方案:
    分析+ 携带token
    
  • 通过发送请求获取请求数据

    """
    反爬原理: 增加请求参数,如果没有携带,或者携带的不符合,则是爬虫。
    """
    
  • 通过js生成请求参数

    """
    使用js动态生成请求参数
    """
    解决方法:分析js,观察加密的实现过程,通过js2py获取js的执行结果,或者使用selenium来实现。
    
  • 通过验证码来反爬

    """
    对方服务器通过弹出验证码强制验证用户浏览行为
    """
    解决方案:打码平台或者是机器学习的方法识别验证码,其中打码平台廉价易用,更值得推荐
    

6.2:爬虫行为反爬

  • 通过请求ip/账号单位时间内总请求数量进行反爬

    """
    正常浏览器请求网站,速度不会太快,同一个ip/账号大量请求了对方服务器,有更大的可能性会被识别为爬虫。
    """
    解决方案:对应的通过购买高质量的ip的方式能够解决问题/购买个多账号。
    
  • 通过同一ip/账号请求之间的间隔进行反爬

    """
    正常人操作浏览器浏览网站,请求之间的时间间隔是随机的,而爬虫前后两个请求之间时间间隔通常比较固定同时时间间隔较短,因此可以用来做反爬。
    """
    解决方案:请求之间进行随机等待,模拟真实用户操作,在添加时间间隔后,为了能够高速获取数据,尽量使用代理池,如果是账号,则将账号请求之间设置随机休眠。
    
  • 通过对请求ip/账号每天请求次数设置阈值进行反爬

    """
    通过对请求ip/账号每天请求次数设置阈值进行反爬
    """
    解决方案:对应的通过购买高质量的ip的方法/多账号,同时设置请求间随机休眠
    
  • 通过js实现跳转来反爬

    """
    实现原理: 一般的界面可以之间接使用链接进行跳转。而我们可以使用js动态生成链接,使用js发送动态请求。这样无法在源码中获取下一页url。
    """
    解决方案:多次抓包,分析动态请求。
    
  • 通过蜜罐(陷阱)获取爬虫ip(或者代理ip),进行反爬

    """
    爬虫的原理:爬虫会根据正则,Xpath,css,等方式获取下一次的链接。我们可以设置一些符合正则的链接。如果爬虫访问到这个链接,我们就封掉这个ip地址。
    """
    解决方案:完成爬虫的编写之后,使用代理批量爬取测试/仔细分析响应内容结构,找出页面中存在的陷阱
    
  • 返回假的数据进行反爬:

    """
    反爬原理:在响应中增加虚假的内容,前端通过虚假内容进行改变展示出真正的数据。
    例如: 返回234,前端通过对234,进行偏移(340),相加(451),得到真正的数据(451)
    """
    解决方案: 分析返回数据与真正的数据之间的关系。
    
  • 阻塞任务队列

    """
    反爬原理: 一旦识别爬虫,我们返回大量的虚假的生成的假的url地址,让爬虫阻塞队列。
    """
    
    解决方案: 分析假的url地址,得到假的url生成规则,对于url进行过滤。
    
  • 阻塞网络IO

    """
    反爬原理:一旦识别爬虫,返回一个大的文件比如10G影视文件,阻塞网络你的IO。
    """
    解决方案: 对于爬虫的线程进行统计时间。
    

6.3:数据加密反爬

  • 1: 数据的特殊化处理:

    """
    例如: 返回的数据,使用自有的字体文件
    """
    解决方案:切换到手机端,字体文件进行翻译。
    
  • 2: CSS偏移反爬:

    """
    源码数据不为真正数据,需要通过css位移才能产生真正数据
    """
    计算css的偏移
    
  • 3:js动态生成数据反爬:

    """
    使用js生成数据
    """
    js反解析。
    
  • 4:数据图片化反爬:

    """
    数据使用图片的形式展示。
    """
    使用图片引擎,解析图片
    
  • 5:编码格式反爬:

    """
    爬虫一般使用utf-8进行解析,而我们不使用utf-8进行解码。
    """
    换解码格式。
    

七:打码平台的使用

7.1: 图片识别引擎tesseract:

  • 图片识别引擎的安装:

    • linux: sudo apt-get install tesseract-ocr

    • windows: https://github.com/UB-Mannheim/tesseract/wiki

    • 具体安装: https://blog.csdn.net/qq_41341757/article/details/110099127

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eowiz6R7-1613550120320)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\image-20210123101000162.png)]

    • 安装pillow图片库:pip install pillow

    • 安装pytesseract 模块: pip install pytesseract

  • 图片识别引擎的使用?

    from PIL import Image
    import pytesseract
    # 创建图片对象
    image = Image.open("图片路径")
    # 识别图片
    result = pytesseract.image_to_string(im)
    print(result)
    

7.2: 使用打码平台识别图像:

  • 打码平台地址:http://www.ttshitu.com/

  • 打码平台官方文档:http://www.ttshitu.com/docs/index.html

    import json
    import requests
    import base64
    from io import BytesIO
    from PIL import Image
    from sys import version_info
    
    from day04练习.captcha.captcha import captcha
    
    # 生成图片验证码
    def get_imgae():
        text, image = captcha.generate_captcha()
        print("我的图片验证码的真实值是:",text)
        with open("image.png", "wb") as f:
            f.write(image)
    
    
    # 打码平台解析工具
    def base64_api(uname, pwd, img):
        img = img.convert('RGB')
        buffered = BytesIO()
        img.save(buffered, format="JPEG")
        if version_info.major >= 3:
            b64 = str(base64.b64encode(buffered.getvalue()), encoding='utf-8')
        else:
            b64 = str(base64.b64encode(buffered.getvalue()))
        data = {"username": uname, "password": pwd, "image": b64}
        result = json.loads(requests.post("http://api.ttshitu.com/base64", json=data).text)
        if result['success']:
            return result["data"]["result"]
        else:
            return result["message"]
    
    
    
    if __name__ == "__main__":
        # 调用我的生成图片验证码函数:
        get_imgae()
        img_path = "./image.png"
        img = Image.open(img_path)
        # 这里用自己的用户名和密码
        result = base64_api(uname='wenbin123', pwd='wenbin321', img=img)
        print("真正解析出来的值是:", result)
    

八:js反解析:

8.1:有道翻译JS逆向分析:

  • 1: 首先分析请求的url地址和亲请求方式: 通过发送请求看看哪个响应返回的是翻译后的响应。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fZ2Mkk2L-1613550120321)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\image-20210123134443957.png)]

    查看请求头:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a67oddTl-1613550120322)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\image-20210123134528777.png)]

​ 确定请求的url和请求的方式:

​ 请求的url : http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule

​ 请求方式: POST

  • 2:多次请求,寻找请求过程中哪些参数是变化的,哪些是不变的。

    我们发现参数中: salt, sign, lts是改变的,其余的是不变的。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bv6aULlh-1613550120323)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\image-20210123135145191.png)]

  • 3:查询这些改变的参数,是在哪些JS中出现的?

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SJ6e4Tp5-1613550120324)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\image-20210123135539021.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uHZHwx5P-1613550120325)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\image-20210123140249443.png)]

  • 4: 分析js代码:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XV0CjJOH-1613550120326)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\image-20210123143201245.png)]

  • 分析结论:

    • i : 要翻译的内容。
    • salt: 13位的时间戳+1位随机整数
    • sign : 固定字符串1 + 要翻译的字 + 盐值 + 随机数 + 固定字符串2 的md5加密
    • ts : 版本号的md5加密。

8.2:MD5加密:

import _hashlib
import hashlib

md5_str = 'python被加密字符串'

# 1: 创建md5对象
md5 = hashlib.md5()

# 2: md5对象增加加密字符串
md5.update(md5_str.encode())

# 3: 获取加密结果:
ret = md5.hexdigest()
print(ret)

8.3: 有道翻译爬虫:

import random
import time
import hashlib
import requests

class YoudaoSpider(object):

    def __init__(self, word):
        """初始化方法"""
        self.url = " http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule"
        self.headers = {
            "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36",
            "Cookie": 'OUTFOX_SEARCH_USER_ID_NCOO=1946753269.7896786; OUTFOX_SEARCH_USER_ID="1040134818@10.169.0.83"; JSESSIONID=aaa74ECzZIxo--iaSw9xx; ___rl__test__cookies=1606276051961',
            "Referer": "http://fanyi.youdao.com/"
        }
        self.word = word
        self.post_body = {}

    def genrator_post_body(self):
        """生成请求体字典方法"""
        # 1:拿到13位的时间戳
        ts = str(int(time.time()*1000))
        # 2: 得到salt = 13位时间戳+1位随机值
        salt = ts + str(random.randint(0, 10))
        # 3: 得到sign = 两个字符串 + 被翻译的值 + 盐值
        sign_str = "fanyideskweb" + self.word + salt + "]BjuETDhU)zqSxf-=B#7m"
        # 4: 将sign_str 用md5进行加密:
        md5 = hashlib.md5()
        md5.update(sign_str.encode())
        sign = md5.hexdigest()
        self.post_body = {
            "i": self.word,
            "from": "AUTO",
            "to": "AUTO",
            "smartresult": "dict",
            "client": "fanyideskweb",
            "salt": salt,
            "sign": sign,
            "ts": ts,
            "bv": "b286f0a34340b928819a6f64492585e8",
            "doctype": "json",
            "version": "2.1",
            "keyfrom": "fanyi.web",
            "action": "FY_BY_REALTlME"
        }

    def send_request(self):
        """发送请求的方法"""
        response = requests.post(self.url, headers=self.headers, data=self.post_body)
        return response.json()

    def parser_response(self, ret_dict):
        # 从字典中提取翻译结果
        # jsonpath:  $..src
        ret = ret_dict["translateResult"][0][0]["tgt"]
        print("翻译的文本:{} 翻译的结果:{}".format(self.word, ret))


    def run(self):
        """运行函数"""
        self.genrator_post_body()
        ret_dict = self.send_request()
        self.parser_response(ret_dict)

if __name__ == '__main__':
    spider = YoudaoSpider('妈妈')
    spider.run()

8.4: 使用python执行js代码:

  • pip install js2py
import js2py

js_str = """
    function func(){
    list = [1, 2, 3, 4, 5, 6, 7, 8, 9];
    var sum = 0;
    for (var i=0; i<list.length; i++){
        sum = sum + list[i];
        };
    return sum;
    }
"""
# 1:构建js环境
js = js2py.EvalJs()
# 2:执行js代码
js.execute(js_str)
# 3: 调用函数:
func_name = "func()"
js.execute("code = {}".format(func_name))
print(js.code)

九:Scrapy框架

官方文档地址:http://scrapy-chs.readthedocs.io/zh_CN/1.0/intro/overview.html

9.1:Scrapy框架的特点:

  • Scrapy框架**Twisted['twɪstɪd]**使用异步网络框架

9.2:Scrapy框架的执行流程:

"""
1: 爬虫模块指定起始的url,经过爬虫中间件,交给引擎。
2:由Scrapy框架(引擎模块)创建出请求对象,将请求对象传入调度器(队列)。
3:引擎模块,在调度器模块取出请求对象,经过下载中间件,交给下载器。
4:下载器,下载完成后,将响应对象经过下载中间件,交给引擎模块。
5:引擎模块经过爬虫中间件将响应交给爬虫模块,爬虫模块解析响应。
6:响应解析出来如果是数据,则响应模块将数据交给引擎模块,引擎模块再交给数据管道模块。
7:响应解析出来如果是url,则再次循环。
"""

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yx4CqTuG-1613550120327)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\image-20210123171940030.png)]

9.3:Scrapy框架了解

  • 虚拟环境的搭建:

    • which python3 查看python3的位置
    • mkvirtualenv 虚拟环境名 -p python3的地址
  • scrapy框架的安装:

    • pip3 install scrapy -i https://pypi.tuna.tsinghua.edu.cn/simple
  • 创建项目指定爬取的范围:

    • cd 桌面
    • scrapy startproject 项目名
    • scrapy genspider <爬虫名字> <允许爬取的域名>
  • windows,配置pycharm远程解释器执行。

  • scrapy框架的结构:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B29zV137-1613550120328)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\image-20210124092205248.png)]

9.4:Scrapy框架爬取马场老师信息

E:\py_project\myspider\myspider\spiders

import scrapy

class ItcastSpider(scrapy.Spider):
    # 爬虫的名字
    name = 'itcast'
    # 爬虫允许爬取的范围
    allowed_domains = ['itcast.cn']
    # 爬虫起始的url地址: Scrapy框架底层会帮我构建请求,发送请求获取响应对象。
    start_urls = ['http://www.itcast.cn/channel/teacher.shtml']

    def parse(self, response):
        """解析url地址,返回响应对象"""
        li_list = response.xpath("//div[@class='tea_con']//ul//li")
        for li in li_list:
            item = {}
            item['name'] = li.xpath(".//h3/text()").extract_first()
            item['level'] = li.xpath(".//h4/text()").extract_first()
            item['desc'] = li.xpath(".//p/text()").extract_first()

            print(item)
  • 执行爬虫:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XpPpqFpY-1613550120329)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\image-20210124094253864.png)]

  • 注意:

    • 在解析函数中提取的url地址如果要发送请求,则必须属于allowed_domains范围内,但是start_urls中的url地址不受这个限制。
    • 解析函数中的yield能够传递的对象只能是:BaseItem, Request, dict, None。
    • response.xpath方法的返回结果是一个类似list的类型,其中包含的是selector对象,操作和列表一样,但是有一些额外的方法。
      • 额外方法extract():返回一个包含有字符串的列表。
      • 额外方法extract_first():返回列表中的第一个字符串,列表为空没有返回None。

9.5:使用管道保存数据:

  • 爬虫代码修改成yield交给引擎:

    import scrapy
    
    class ItcastSpider(scrapy.Spider):
        # 爬虫的名字
        name = 'itcast'
        # 爬虫允许爬取的范围
        allowed_domains = ['itcast.cn']
        # 爬虫起始的url地址: Scrapy框架底层会帮我构建请求,发送请求获取响应对象。
        start_urls = ['http://www.itcast.cn/channel/teacher.shtml']
    
        def parse(self, response):
            """解析url地址,返回响应对象"""
            li_list = response.xpath("//div[@class='tea_con']//ul//li")
            for li in li_list:
                item = {}
                item['name'] = li.xpath(".//h3/text()").extract_first()
                item['level'] = li.xpath(".//h4/text()").extract_first()
                item['desc'] = li.xpath(".//p/text()").extract_first()
    
                yield item
    
  • 管道代码:E:\py_project\myspider\myspider\pipelines.py

    class MyspiderPipeline:
        def process_item(self, item, spider):
            """
            :param item: 数据,是引擎将爬虫模块的传递过来的[模型类或者字典]
            :param spider:爬虫对象,spider.name根据不同的爬虫名,做不同的处理
            :return:
            """
            if spider.name == "itcast":
                print("爬虫名称:{}===============>".format(spider.name))
                print(item)
            return item
    
  • 配置文件配置管道:E:\py_project\myspider\myspider\settings.py

    增加配置项:

    ITEM_PIPELINES = {
        # key: 管道路径, value是权重,执行的优先级
       'myspider.pipelines.MyspiderPipeline': 300,
    }
    
  • 再次执行:scrapy crawl 爬虫名

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FZaK8sYS-1613550120330)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\image-20210124101256288.png)]

9.6:爬虫的数据模型:

  • 在E:\py_project\myspider\myspider\items.py中构造数据模型。

  • 案例: 我们要爬取老师的名字,职称,描述信息。

    在items.py中定义这三个字段:

    import scrapy
    class ItcastItem(scrapy.Item):
        # 讲师的名字
        name = scrapy.Field()
        # 讲师的职称
        level = scrapy.Field()
        # 讲师的介绍
        text = scrapy.Field()
    

    在爬虫代码中使用这三个模型类字段:

    import scrapy
    from myspider.items import ItcastItem
    class ItcastSpider(scrapy.Spider):
        # 爬虫的名字
        name = 'itcast'
        # 爬虫允许爬取的范围
        allowed_domains = ['itcast.cn']
        # 爬虫起始的url地址: Scrapy框架底层会帮我构建请求,发送请求获取响应对象。
        start_urls = ['http://www.itcast.cn/channel/teacher.shtml']
    
        def parse(self, response):
            """解析url地址,返回响应对象"""
            li_list = response.xpath("//div[@class='tea_con']//ul//li")
            for li in li_list:
                item = ItcastItem()
                item["name"] = li.xpath(".//h3/text()").extract_first()
                item["level"] = li.xpath(".//h4/text()").extract_first()
                item["text"] = li.xpath(".//p/text()").extract_first()
    
                yield item
    

9.7 爬虫翻页请求:

  • 翻页请求的思路:

    """
    1: 获取下一页的url
    2: 构造新的Request请求对象
    3: 使用yield关键字将Request对象交给引擎。
    """
    
  • 网页招聘爬虫:

    • 创建jobspider爬虫:scrapy genspider jobspider https://hr.163.com/

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SFPAsFlz-1613550120331)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\image-20210124113602128.png)]

    • 在settings中设置ROBOTS协议

      # Obey robots.txt rules
      # False表示忽略网站的robots.txt协议,默认为True
      ROBOTSTXT_OBEY = False
      
    • 配置文件中设置关闭User-Agent:

      # scrapy发送的每一个请求的默认UA都是设置的这个User-Agent
      USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36'
      
    • 构建数据模型:

      class JobItem(scrapy.Item):
          # 职位名称
          name = scrapy.Field()
      
    • 编写爬虫代码:

      import scrapy
      from myspider.items import JobItem
      
      
      class JobspiderSpider(scrapy.Spider):
          # 爬虫名称
          name = 'jobspider'
          # 爬虫允许爬取的范围
          allowed_domains = ['https://hr.163.com/']
          # 爬虫的起始url
          start_urls = ['https://hr.163.com/position/list.do']
      
          def parse(self, response):
      
              # 提取响应数据中的tr列表
              tr_list = response.xpath("//table[@class='position-tb']//tr")
              # 去除数据为空的tr标签
              tr_list = [tr for tr in tr_list if tr_list.index(tr) % 2 != 0]
      
              # 遍历取出每一条数据
              for tr in tr_list:
                  # 创建模型类对象
                  item = JobItem()
                  # 提取职位名称
                  item["name"] = tr.xpath("./td[1]/a/text()").extract_first()
                  print(item)
      
                  # 获取下一页的url地址
                  next_url = response.xpath("//a[text()='>']/@href").extract_first()
                  # 判断 是否是最后一页
                  if next_url != "javascript:void(0)":
                      # 1.构造完整的url地址
                      full_url = self.start_urls[0] + next_url
                      # 2.利用scrapy.Request构建下一页的请求对象,并yield给引擎
                      # callback指定请求之后的响应对象使用哪一个方法进行解析
                      yield scrapy.Request( url=full_url, callback=self.parse, dont_filter=True)
      
      
    • 测试代码的运行:scrapy crawl jobspider

9.8:不同解析函数之间进行传递(meta参数):

  • 需求: 同样的一个模型类,需要记录两个解析函数中的值,此时就需要第一个解析函数将Item对象传递给第二个解析函数。

  • 例如:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yO76u4Pm-1613550120332)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\image-20210124152444755.png)]

  • 代码:

    """
    思路: 首先引擎爬取首页,然后交给第一个解析函数解析,第一个解析函数使用yield关键字的时候传递给第二个函数一个mata参数,这个参数携带Item对象。然后引擎继续抓取详情页,响应交给第二个解析函数,第二个在响应对象中获取Item对象。
    """
    
    import scrapy
    from ..items import JobDetaiItem
    
    
    class JobDetailSpider(scrapy.Spider):
        """
        需求:爬取首页的职位名称,然后将item对象传给详情页,爬取详情页的发布时间
        """
        name = 'job_detail'
        allowed_domains = ['163.com']
        start_urls = ['https://hr.163.com/position/list.do']
    
        def parse(self, response):
            # 1.从响应对象中提取tr标签列表
            tr_list = response.xpath("//table[@class='position-tb']//tr")
    
            # 2.过滤掉空的tr标签
            tr_list = [tr for tr in tr_list if tr_list.index(tr) % 2 != 0]
    
            # 3.遍历提取职位名称
            for tr in tr_list:
                # 创建模型对象
                item = JobDetaiItem()
                # extract_first() 提取列表中第一条数据
                item["name"] = tr.xpath("./td[1]/a/text()").extract_first()
    
                # 往详情页发起请求,抓取数据
                detail_url = tr.xpath("./td[1]/a/@href").extract_first()
                # 1.构建详情页完整url地址
                full_path = "https://hr.163.com/" + detail_url
                # 2.构建请求,
                request = scrapy.Request(url=full_path,
                                         callback=self.parse_detail,
                                         meta={"item": item})
                # 3.并且yield给引擎
                yield request
    
        def parse_detail(self, response):
            """
            解析详情页面的响应
            :param response: 详情页响应对象
            :return:
            """
            # 1.从响应中获取item模型对象
            item = response.meta["item"]
            # 2.提取发布时间数据添加到模型对象中
            item["date"] = response.xpath("//p[@class='post-date']/text()").extract_first()
            print(item)
    

9.9:Scrapy,Request的参数:

  • scrapy.Request(url[,callback,method="GET",headers,body,cookies,meta,dont_filter=False])
    
  • dont_filter : 默认过滤重复请求,如果设置False表示不过滤该请求。

9.10:百度翻译爬虫案例:

  • 核心技术点:

  • 案例:百度翻译:

    • 1:百度翻译分析:

      • 百度翻译网址: https://fanyi.baidu.com/

      • 百度翻译的具体地址: https://fanyi.baidu.com/v2transapi?from=zh&to=en

      • 分析请求的参数:发现请求参数sign是改变的其余的是不变的。

        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hzMoFyLP-1613550120333)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\image-20210125202136531.png)]

      • 分析sign签名的生成过程:

        通过查询js分析出:

        langIsDeteced: function(a, n, r, s) {
                    if (null !== a) {
                        var o = $(".select-from-language .language-selected").attr("data-lang")
                          , l = $(".select-to-language .language-selected").attr("data-lang")
                          , g = "1" === $(".select-from-language .language-selected").attr("data-detected")
                          , c = !1;
                        d.get("langChangedByUser") && a === l && (c = !0);
                        var p = null;
                        if (s && !d.get("fromLangIsAuto") && o !== a ? p = i.processOcrLang(a, o, l) : (e.show(a, o),
                        p = i.getLang(a, o, l)),
                        "LAN-UNKNOWN" === a && ("auto" === o || g)) {
                            var m = t("translation:widget/translate/input/clear");
                            return void m.clearOutput()
                        }
                        u.show(),
                        $("body").trigger("updateFromTo.domainTrans", p);
                        var h = this
                          , n = this.processQuery(n)
                          , w = {
                            from: p.fromLang,
                            to: p.toLang,
                            query: n,
                            transtype: r,
                            simple_means_flag: 3,
                            sign: f(n),
                            token: window.common.token,
                            domain: y.getCurDomain()
                        };
                        y.log(),
                        this.translateXHR && 4 !== this.translateXHR.readyState && this.translateXHR.abort(),
                        this.translateXHR = $.ajax({
                            type: "POST",
                            url: "/v2transapi?from=" + encodeURIComponent(p.fromLang) + "&to=" + encodeURIComponent(p.toLang),
                            cache: !1,
                            data: w
                        }).done(function(t) {
                            d.set("isInRtTransState", !0),
                            h.translateSuccess(t, p.fromLang, p.toLang, n, c)
                        })
                    }
                },
        

        核心代码:也就是说sign值= f(n),n经过断点分析发现就是我们被翻译的字。

        sign: f(n),
        token: window.common.token,
        

        追踪源码发现:这段代码 return p = n(p, D),这个p就是我们的sign的值。

            function n(r, o) {
                for (var t = 0; t < o.length - 2; t += 3) {
                    var a = o.charAt(t + 2);
                    a = a >= "a" ? a.charCodeAt(0) - 87 : Number(a),
                    a = "+" === o.charAt(t + 1) ? r >>> a : r << a,
                    r = "+" === o.charAt(t) ? r + a & 4294967295 : r ^ a
                }
                return r
            }
            function e(r) {
                var o = r.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g);
                if (null === o) {
                    var t = r.length;
                    t > 30 && (r = "" + r.substr(0, 10) + r.substr(Math.floor(t / 2) - 5, 10) + r.substr(-10, 10))
                } else {
                    for (var e = r.split(/[\uD800-\uDBFF][\uDC00-\uDFFF]/), C = 0, h = e.length, f = []; h > C; C++)
                        "" !== e[C] && f.push.apply(f, a(e[C].split(""))),
                        C !== h - 1 && f.push(o[C]);
                    var g = f.length;
                    g > 30 && (r = f.slice(0, 10).join("") + f.slice(Math.floor(g / 2) - 5, Math.floor(g / 2) + 5).join("") + f.slice(-10).join(""))
                }
                var u = void 0
                  , l = "" + String.fromCharCode(103) + String.fromCharCode(116) + String.fromCharCode(107);
                u = null !== i ? i : (i = window[l] || "") || "";
                for (var d = u.split("."), m = Number(d[0]) || 0, s = Number(d[1]) || 0, S = [], c = 0, v = 0; v < r.length; v++) {
                    var A = r.charCodeAt(v);
                    128 > A ? S[c++] = A : (2048 > A ? S[c++] = A >> 6 | 192 : (55296 === (64512 & A) && v + 1 < r.length && 56320 === (64512 & r.charCodeAt(v + 1)) ? (A = 65536 + ((1023 & A) << 10) + (1023 & r.charCodeAt(++v)),
                    S[c++] = A >> 18 | 240,
                    S[c++] = A >> 12 & 63 | 128) : S[c++] = A >> 12 | 224,
                    S[c++] = A >> 6 & 63 | 128),
                    S[c++] = 63 & A | 128)
                }
                for (var p = m, F = "" + String.fromCharCode(43) + String.fromCharCode(45) + String.fromCharCode(97) + ("" + String.fromCharCode(94) + String.fromCharCode(43) + String.fromCharCode(54)), D = "" + String.fromCharCode(43) + String.fromCharCode(45) + String.fromCharCode(51) + ("" + String.fromCharCode(94) + String.fromCharCode(43) + String.fromCharCode(98)) + ("" + String.fromCharCode(43) + String.fromCharCode(45) + String.fromCharCode(102)), b = 0; b < S.length; b++)
                    p += S[b],
                    p = n(p, F);
                return p = n(p, D),
                p ^= s,
                0 > p && (p = (2147483647 & p) + 2147483648),
                p %= 1e6,
                p.toString() + "." + (p ^ m)
            }
            var i = null;
            t.exports = e
        });
        
    • 2: 百度翻译代码编写:

      • 生成百度爬虫:scrapy genspider baidu_spider https://fanyi.baidu.com/
    • 2:百度翻译代码:

9.11:发送Post请求:

  • 方案一: 使用scrapy.Request(url , data, callback, method)发送post请求。

  • 方案二: 使用scrapy.FormRequest(url, formdata, callback)来发送post请求。

  • 案例: 发送post请求:

  • # 重写构建请求方法
       def start_requests(self):
           request_params = {
               "f": "auto",
               "t": "auto",
               "w": "你好",
           }
                   # 构建POST请求
           yield scrapy.FormRequest(url=self.start_urls[0], formdata=request_params, callback=self.parse)
    

9.12:Scrapy框架处理Cookie:

  • 需要重写start_requests方法,然后准备cookie字符串,然后将cookie字符串转换成字典,然后在请求中携带上cookie。

    # 重写start_urls起始URL地址构建请求的方法
        def start_requests(self):
              # 1.准备cookie字符串
            cookie_str = "复制cookie字符串"
                    # 2.cookie字符串转换成字典
            cookie_dict = {cookie.split("=")[0]: cookie.split("=")[1] for cookie in cookie_str.split("; ")}
                    # 3.自己构建一个GET请求,携带cookie参数
            for url in self.start_urls:
                     # 4.yield将请求交给引擎
                yield scrapy.Request(url=url, cookies=cookie_dict, callback=self.parse)
    
  • 案例: Scrapy框架模拟登录:github模拟登录个人中心:

    """
    方案一: 请求登录之后直接携带cookie。
    方案二: 
    """
    
    • 新建爬虫项目----github_spider :

      scrapy genspider github_spider https://github.com/

    • 案例分析:

      """
      1:请求路径:https://github.com/renshanwen
      2:重写start_request方法,然后使用yield关键字发送请求,将响应的结果交给parse解析响应。
      """
      
    • 案例代码:

      import scrapy
      class GithubSpiderSpider(scrapy.Spider):
          name = 'github_spider'
          allowed_domains = ['https://github.com/']
          start_urls = ['https://github.com/renshanwen']
      
      
          def start_requests(self):
              # 1: 准备cookie字符串
              cookie_str = '_octo=GH1.1.1225176107.1598225837; _ga=GA1.2.830840961.1598243639; _device_id=8fea1d9b11a138bf360d7171491b8242; tz=Etc/GMT+8; has_recent_activity=1; user_session=RkjQsbjr5Fm1PHfweoe8yTHb3yFnYVDzpes5dqRdTvNoW6Tm; __Host-user_session_same_site=RkjQsbjr5Fm1PHfweoe8yTHb3yFnYVDzpes5dqRdTvNoW6Tm; tz=Etc/GMT+8; logged_in=yes; dotcom_user=renshanwen; _gh_sess=MnG4TT6211f/zUNbf3WcFN1aV8agVHAsnDRnsEd4DGDBzIrq0OQk2utx0yBTamMUyRL51ypfPa6vhXHaSk8ECD7EhyMIyJ6DpgKatuJbKAUYKpXqJzu67SFDhGNrvXeApnGrr0DSZHUa9U68eA3HpeX2y5k0c8Q5E8h4E3Qj/LSfJcSyrhnNt5oMv+WODV6ge2FMhNGPaODqV1kUiIT7wz2iT9b6bmGUrhnRxeHEPgDl5fZezz7EXCvQyckKC7lEG70DITx3kWYwy12BfwJWxFBfMiZ0SI7Nq6Y3dBWAREXWiDptUiEGQtzOx/SnDMKVcYDfKMMlH/OeWz2jadLarb/Y1SEQ2raABwXubRvpWs82CAnfCARb4ZCdBRlxXRVgVqQUPYNqKkobdZttWMr8pgVwFZc+RT9bouTAQNWbjLEQOXawz5dMDdmScUplPYC7M/pPE7Q0LKQqaeEqru3ndbxgWySBaxOE3WgxSXahf8oJ2bG8//bK3VB6QlGDvXXIgpxEuT4Vw4IPcNRMOQXas98nfZctzfxVxgQtJjzj7ck5YgB9sfd9SM+YNa0B+tkj/rt8nKZY+1N9rs5ieGKV7ay0ZZpr+kUs/AIropPvfal0M9tVLmxa62QP+9L27j2U6/1sju/ySgoM43CrnR3ZuHSo5YEc7UvmNeccb+pNLgnWrrKDR49LXV4hgFiNLnjVaHk01EsjOfI0e9CGmvNd+fyoc3AOKplnjhTU5m9bGJCGrl+gbyZUlBNJHEE2Q6Nyt7FH6Cnvb3M=--02XcVQT//S2B/emb--TAckSQpoBAsk0sWY7puLlQ=='
      
              # 2: 将cookie字符串转换成dict
              cookie_dict = {cookie.split("=")[0] :cookie.split("=")[1] for cookie in cookie_str.split("; ")}
      
              # 3: 发送请求交给引擎:
              for url in self.start_urls:
                  yield scrapy.Request(url= url, cookies= cookie_dict,callback=self.parse)
      
      
          def parse(self, response):
              with open("github.html","w", encoding='utf-8' ) as f:
                  f.write(response.body.decode())
      
    • 启动爬虫:scrapy crawl github_spider

9.13:Scrapy框架实现自动提交表单:

  • scrapy.Formrequest.from_response 能够在响应中寻找form表单,然后把formdata中的数据提交到action对象对应的url中去。
    import scrapy
    
    
    class GitHubSpiderSpider(scrapy.Spider):
        name = 'git_hub_spider'
        allowed_domains = ['github.com']
        start_urls = ['https://github.com/login']
    
        def parse(self, response):
            yield scrapy.FormRequest.from_response(response=response,
                                                   formxpath="//div[@id='login']//form",
                                                    formdata={"login": "1173714240@qq.com", "password": "ren4562758"},
                                                    callback=self.login_parse
                                                   )
        def login_parse(self, response):
            print(response.body.decode())
            with open("github_login.html", "w", encoding='utf-8') as f:
                f.write(response.body.decode())
    
    
    
  • 注意事项:

    1:首先向登录界面发送请求,获取请求的界面
    2:在请求的界面中获取from表单,如果页面只有一个则无需获取表单。
    3:在表单中根据表单元素的name填充key,根据元素的value填充值。
    4: 然后根据scrapy.FormRequest.from_response发送请求,得到登录后的界面。
    5:交给解析函数,保存解析数据。
    

9.14:Scrapy管道的使用:

  • 管道的方法:

    • 创建管道:

      class MyspiderPipeline:
          def process_item(self, item, spider):
              """
              :param item: 数据,是引擎将爬虫模块的传递过来的[模型类或者字典]
              :param spider:爬虫对象,spider.name根据不同的爬虫名,做不同的处理
              :return:
              """
              if spider.name == "itcast":
                  print("爬虫名称:{}===============>".format(spider.name))
                  print(item)
              return item
      
    • 开启管道: open_spider(self, spider)

    • 关闭管道: close_spider(self, spider)

  • 案例: 利用管道将数据存储到json文件中。

    """
    1: 首先爬虫模块构建请求,然后交给引擎。
    2:引擎下载数据响应交给parse解析
    3:解析后的结果交给引擎,引擎将结果交给数据管道。
    4: 管道中,open_spider,打开文件,process_item保存数据,然后close_spider,关闭文件
    """
    
    import scrapy
    from myspider.items import ItcastItem
    class ItcastSpider(scrapy.Spider):
        # 爬虫的名字
        name = 'itcast'
        # 爬虫允许爬取的范围
        allowed_domains = ['itcast.cn']
        # 爬虫起始的url地址: Scrapy框架底层会帮我构建请求,发送请求获取响应对象。
        start_urls = ['http://www.itcast.cn/channel/teacher.shtml']
    
        def parse(self, response):
            """解析url地址,返回响应对象"""
            li_list = response.xpath("//div[@class='tea_con']//ul//li")
            for li in li_list:
                item = ItcastItem()
                item["name"] = li.xpath(".//h3/text()").extract_first()
                item["level"] = li.xpath(".//h4/text()").extract_first()
                item["text"] = li.xpath(".//p/text()").extract_first()
                yield item
    
    ITEM_PIPELINES = {
        # key: 管道路径, value是权重,执行的优先级
       'myspider.pipelines.MyspiderPipeline': 300,
    }
    
    import json
    
    from itemadapter import ItemAdapter
    
    
    class MyspiderPipeline:
    
        def open_spider(self, spider):
            """
            爬虫的初始化工作
            :param spider:
            :return:
            """
            if spider.name == 'itcast':
                self.f = open("teacher.json", 'w')
    
        def process_item(self, item, spider):
            """
            :param item: 数据,是引擎将爬虫模块的传递过来的[模型类或者字典]
            :param spider:爬虫对象,spider.name根据不同的爬虫名,做不同的处理
            :return:
            """
            if spider.name == "itcast":
                item_dict = dict(item)
                json_str = json.dumps(item_dict, ensure_ascii=False, indent=4)
                self.f.write(json_str + ",\n")
            return item
    
        def close_spider(self, spider):
            """
            爬虫结束的时候调用这个方法
            :param spider:
            :return:
            """
            if spider.name == 'itcast':
                self.f.close()
    
  • 将数据存储到mongodb数据库中:

  • 注意事项:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rj6pKnQq-1613550120334)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612007038595.png)]

9.15:crawlspider爬虫:

  • 作用: 我们构建一个初始的请求,crawlspider会在响应中自动的提取url地址,然后重新发送请求。

  • 创建crawlspider爬虫:scrapy genspider -t crawl job 163.com

  • 爬虫代码的实现:

    import scrapy
    from scrapy.linkextractors import LinkExtractor
    from scrapy.spiders import CrawlSpider, Rule
    
    
    # 1: 首先继承的类不同了,这里继承CrawlSpider,并且没有parse函数
    class JobSpider(CrawlSpider):
        name = 'job'
        allowed_domains = ['163.com']
        start_urls = ['https://hr.163.com/position/list.do']
    
        # rules是指定响应对象中要提取的url地址:rules要么是元祖类型,要么是列表类型。
        rules = (
            # Rule是规则对象
            # LinkExtractor是连接提取器,可以通过正则。xpath语法提取url地址
            # callback 是提取后的地址请求后的响应要被交给parse_item函数解析
            # follow 表示: 在下一页中是否继续按照这个语法提取,如果是则是True
            #Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
            # 获取详情页连接
            Rule(LinkExtractor(allow=r'/position/detail.do\?id=\d+'), callback='parse_item'),
            # 获取翻页连接
            Rule(LinkExtractor(allow=r'\?currentPage=\d+'), follow=True),
        )
    
        def parse_item(self, response):
            item = {}
            item["title"] = response.xpath("//h2/text()").extract_first()
            print(item)
    

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eGNRjUck-1613550120335)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612009737595.png)]

9.16:Scrapy中间件

  • 中间件的作用:

    • 下载中间件:请求携带header, 携带cookie,使用代理IP,

    • 爬虫中间件:

  • 中间件的配置都在middleware.py中。

  • 中间件的方法:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MKlaDucN-1613550120336)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612011194376.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6WVuqmLZ-1613550120336)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612010624094.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BTH9kiCs-1613550120337)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612011240883.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AxhFbp0s-1613550120338)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612010753657.png)]

9.17 随机User-Agent中的下载中间件:

  • 在配置文件配置一个User-Agent列表:

    USER_AGENTS_LIST = [
        "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
        "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
        "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
        "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
        "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
        "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
        "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
        "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5"
    ]
    
  • 在middleware中间件中编写请求中间件和下载中间件:

    class UserAgentMiddleware(object):
    
        def process_request(self, request, spider):
            # 作用:处理请求对象,往请求对象中添加User-Agent,携带cookie,设置代理proxy
    
            # 需求:往请求对象中添加随机UA
    
            # 1.从UA列表中随机选取一个UA
            ua = random.choice(USER_AGENTS_LIST)
    
            # 2.添加到request对象中
            request.headers["User-Agent"] = ua
    
            # 不必return请求对象,这样请求对象经过该中间件还可以传入到优先级的中间件进一步处理,最后进入下载器
    
    
    class CheckUserAgent(object):
    
        def process_response(self, request, response, spider):
            """下载中间件-拦截处理响应"""
    
            user_agent = request.headers["User-Agent"]
            print("======>", user_agent)
    
            # 必须return响应,否则引擎获取不到响应,进而爬虫获取不到响应
            return response
    
  • 在settings配置文件中配置两个中间件:

    DOWNLOADER_MIDDLEWARES = {
       'itcast.middlewares.UserAgentMiddleware': 543,
       'itcast.middlewares.CheckUserAgent': 544,
    }
    

9.18: 代理IP的使用:

  • 在配置文件中配置一个IP池:

  • 在中间件中将请求对象的meta字典中设置随机任意的代理IP

    import base64
    
    # 收费的代理ip服务器地址,这里是abuyun
    proxyServer = 'http://proxy.abuyun.com:9010'
    proxyUser = 用户名
    proxyPass = 密码
    # 通过账号密码构建权限认证字符串
    proxyAuth = "Basic " + base64.b64encode(proxyUser + ":" + proxyPass)
    
    class ProxyMiddleware(object):
        def process_request(self, request, spider):
            # 设置代理
            request.meta["proxy"] = proxyServer
            # 设置认证
            request.headers["Proxy-Authorization"] = proxyAuth
    
    class ProxyMiddleware(object):
        def process_request(self,request,spider):
            # 可以在settings.py中构建代理列表proxy_list
            # proxy = random.choice(proxy_list) 
    
            # 免费的会失效,报 111 connection refused 信息!重找一个代理ip再试
            proxy = 'https://1.71.188.37:3128' 
            # 在请求对象的meta字典中设置代理
            request.meta['proxy'] = proxy
    
            # 可以不写return,如果有必要请求对象交给下一个优先级更低的中间件进一步处理
    
  • 在响应方法中判断代理ip是否可用:

    class ProxyMiddleware(object):
    
        def process_response(self, request, response, spider):
    
                    # 如果响应失败了
            if response.status != '200':
                  # 重新发送请求, 请求对象能够再次进入队列
                request.dont_filter = True 
                # 返回响应,请求再次回到调度器队列
                return request
    
  • 在配置文件中配置中间件:

9.19: selenium在Scrapy中使用:

  • 案例: 使用selenium模拟用户登录github,然后拿到该用户的cookie信息。使用这个cookie再访问个人中心界面,保存个人中心界面。

  • 代码实现:

  • 准备工作:

    • 虚拟环境安装 selenium: pip install selenium

    • 查看ubuntu中谷歌浏览器的版本: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oLVDIaVU-1613550120338)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612013919664.png)]

    • 我这个谷歌版本不存在,因此需要更新最新版本的谷歌浏览器。

      • 访问这个网址下载谷歌浏览器linux版本的: https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb

      • 在终端中执行这个命令: sudo apt install libappindicator1 libindicator7

      • 将下载好的压缩文件上传到ubuntu桌面,然后终端cd到桌面。

      • 执行命令: sudo dpkg -i google-chrome-stable_current_amd64.deb

      • 执行命令修复依赖关系: sudo apt -f install

      • 查看现在ubuntu的谷歌版本:

        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-65ntKDy4-1613550120339)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612016530168.png)]

    • 访问网址下载版本对应的驱动器,将驱动复制到ubuntu桌面:

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ztvOyyGz-1613550120340)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612016621947.png)]

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lkQmHPQN-1613550120340)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612016653908.png)]

    • 将 chromedriver 移动到python3所在的目录中:

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zJiFaSdi-1613550120341)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612017106998.png)]

  • 创建新的爬虫: scrapy genspider github_selenium_spider github.com

  • 爬虫代码:

    import scrapy
    
    
    class GithubSeleniumSpiderSpider(scrapy.Spider):
        name = 'github_selenium_spider'
        allowed_domains = ['github.com']
        start_urls = ['https://github.com/renshanwen'] # 向个人中心发送请求
        # 发送请求后经过中间件需要给request对象设置cookie
        def parse(self, response):
            with open("github_selenium.html", 'w') as f:
                f.write(response.body.decode())
    
  • 中间键代码:

    from selenium import webdriver
    import time
    
    def cookie_dict_from_selenium():
        """利用selenium模拟登录,登录成功后获取cookies字符串"""
    
        # 1.设置selenium驱动可选项-无界面访问
        options = webdriver.ChromeOptions()
        options.add_argument("--headless")
        options.add_argument("--disable-gpu")
        # 2.构建selenium驱动对象
        # 注意:需要写绝对路径
        driver = webdriver.Chrome(chrome_options=options)
        driver.get("https://github.com/login")
        # 3.输入账号密码点击登录
        driver.find_element_by_id("login_field").send_keys("1173714240@qq.com")
        time.sleep(2)
        driver.find_element_by_id("password").send_keys("ren4562758")
        time.sleep(2)
        driver.find_element_by_name("commit").click()
    
        # 3.1 强制等待,等页面加载完毕
        time.sleep(3)
        # 3.2 获取cookie字典
        cookie_dict = {cookie["name"]: cookie["value"] for cookie in driver.get_cookies()}
        # 4.退出
        driver.quit()
        # 返回cookie字典
        return cookie_dict
    
    
    class LoginDownloaderMiddleware(object):
    
        def process_request(self, request, spider):
            """在发送请求之前,拦截请求对象,设置cookie信息"""
    
            # 需要判断是起始url地址的请求对象,我们才给他设置cookie
            if request.url == spider.start_urls[0]:
                request.cookies = cookie_dict_from_selenium()
            # 注意不需要返回request
    
  • 配置文件:

    DOWNLOADER_MIDDLEWARES = {
       'itcast.middlewares.LoginDownloaderMiddleware': 543,
    }
    
  • 运行测试: 生成文件:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OwBGx0Bj-1613550120342)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612020279263.png)]

9.20:Scrapy日志与配置文件:

  • 是否遵守robots协议: ROBOTSTXT_OBEY = False

  • 设置User-Agent: USER_AGENT = “ ”

  • 设置默认的请求头: DEFAULT_REQUEST_HEADERS

  • 管道设置: ITEM_PIPELINES , 左位置右权重:权重值越小,越优先执行

  • 爬虫中间件设置: SPIDER_MIDDLEWARES

  • 下载中间件设置: DOWNLOADER_MIDDLEWARES

  • Cookie设置: COOKIES_ENABLED = True, 默认为True表示开启cookie传递功能,即每次请求带上前一次的cookie,做状态保持

  • COOKIES_DEBUG = False , 表示日志中不显示cookie的传递过程 。

  • 修改日志的级别: LOG_LEVEL 默认为DEBUG,控制日志的等级

  • 设置日志的保存路径: LOG_FILE 设置log日志文件的保存路径,如果设置该参数,日志信息将写入文件,终端将不再显示,且受到LOG_LEVEL日志等级的限制 。

十一:Scrapy-redis分布式爬虫概念和源码分析

11.1: Scrapy的概念:

scrapy实际上就是scrap框架的基础上增加了redis分布式组件。

  • scrapy-redis的作用:
  • 通过持久化请求队列和请求的指纹集合来实现。
    • 断点续爬:例如:redis队列中存储了三个请求, 此时scrapy_redis取出其中的一个请求,然后redis宕机了,等redis再次开机的时候,能够继续执行队列的剩余的两个请求。
    • 分布式快速爬取。
  • scrapy-redis的安装:
    • pip install scrapy-redis

11.2:scrapy_redis的执行流程:

  • 1:原来的scheduler是将请求装入内存队列中,现在是将我们的请求队列装入Redis数据库中。

  • 2:我们数据处理的管道原来存储到内存中,现在也存储到redis数据库中。

  • 综上所述: Redis数据库的作用: redis存储请求对象,实现断点续爬,分布式爬取可以根据请求指纹集合对请求去重。数据存储, 使用redis数据库存储数据。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OUX1MfI8-1613550120342)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612416896181.png)]

11.3:分析scrapy_redis如何实现断点续爬

  • 了解配置信息:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qOlaOn7J-1613550120343)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612418026821.png)]

  • 查看demo案例:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i3rRjhfq-1613550120344)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612418360262.png)]

  • 断开程序,然后继续执行,发现会在刚才停止的位置,继续爬取。

11.4:Scrapy_redis源码分析:

  • 1: 分析item数据存入到指定的Redis数据库的管道类中: RedisPipeline。

    • 首先根据配置文件找到RedisPipeline数据库的位置。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9pHduYWb-1613550120345)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612418716572.png)]

    • 找到RedisPipline类:

      我们发现,_process_item对象将item对象中的key和value取到,然后调用scrapy中的方法使用rpush方法将键值对存储到redis数据库中。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NztPGYci-1613550120346)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612419084007.png)]

  • 2: 指纹去重:RFDupeFilter

    • 根据配置文件找到指纹去重类:

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cVXEQBdH-1613550120347)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612420230609.png)]

    • 找到RFDupeFilter类:

      ​ request_seen会根据请求对象,产生一个指纹字符串,然后将这个指纹字符串增加进入redis数据库的。如果这个指纹字符串存在了,说明已经发送过这个请求了,或者已经添加进入调度器了。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4tPQwkJ4-1613550120348)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612420729829.png)]

    • RFDupeFilter, 是如何生成指纹字符串的呢?

      我们发现首先创建一个加密对象,然后利用这个加密对象对请求方法,请求的url地址,以及请求体进行加密,而且还要把请求头中的session_id剔除了,然后再进行加密。最后生成一个16进制的加密字符串。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LRdO1osc-1613550120348)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612422515614.png)]

  • 调度器:将请求加入到redis队列中去 。

    • 找到调度器:

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ySJRJgii-1613550120349)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612423045070.png)]-

    • 在调度器中我们发现有这个方法,enqueue_request方法,如果开启指纹过滤,并且指纹已经存在则添加到调度器失败,如果请求指纹在redis集合中不存在则添加进入redis集合。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4fjxRsON-1613550120350)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612423419081.png)]

  • 持久化request队列和request指纹集合:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IZOShu7Y-1613550120351)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612424289745.png)]

十二: Scrapy_redis分布式爬虫的使用:

12.1: 爬虫demo案例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-suB7x3Fq-1613550120353)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612427907395.png)]

12.2: Scrapy_splash 组件

  • scrapy_splash组件的作用:

    """
    我们之前发送请求返回的响应(页面)是没有渲染前的页面,如果想要渲染后的页面就需要
    scrapy_splash
    scrapy-splash能够模拟浏览器加载js,并返回js运行后的数据
    """
    
  • 组件的安装: 使用docker安装:

    • 首先修改docker的镜像源:

    • sudo vi /etc/docker/daemon.json
      
    • # 最佳信息
      { 
      "registry-mirrors": ["https://registry.docker-cn.com"] 
      }
      
    • sudo docker pull scrapinghub/splash

  • splash的启动:

    • 前台启动: sudo docker run -p 8050:8050 scrapinghub/splash

    • 后台启动: sudo docker run -d -p 8050:8050 scrapinghub/splash

    • 访问地址: 127.0.0.1: 8050

  • splash的退出:

    • 需要先关闭容器,然后删除容器。

    • sudo docker ps -a
      sudo docker stop CONTAINER_ID
      sudo docker rm CONTAINER_ID
      
  • 在虚拟环境中安装splash:

    • pip install scrapy-splash

12.3: Scrapy_splash组件的使用:

  • 创建一个爬虫项目:

    • 进入虚拟环境: workon
    • 创建爬虫项目 : scrapy startproject scrapy_redis_project
  • 创建两个爬虫:

    • scrapy genspider no_splash XXX
    • scrapy genspider with_splash XXX
  • 首先发送一个不带splash的百度搜索请求:

    • 代码:

      import scrapy
      class NoSplashSpider(scrapy.Spider):
          name = 'no_splash'
          allowed_domains = ['baidu.com']
          start_urls = ['https://www.baidu.com/s?wd=马云']
      
          def parse(self, response):
              with open("./no_splash.html", "w", encoding="utf-8") as f:
                  f.write(response.body.decode())
      
    • 得到一个网页: 一共5748行:

  • 然后发送一个不带splash的百度搜索请求:

    • import scrapy
      
      
      # 1: 首先导入SplashRequest这个类
      from scrapy_splash import SplashRequest
      
      
      class WithSplashSpider(scrapy.Spider):
          name = 'with_splash'
          allowed_domains = ['baidu.com']
          start_urls = ['https://www.baidu.com/s?wd=马云']
      
      
          # 2: 重写start——request方法:
          def start_requests(self):
              # 3: 构造SplashRequest请求
              splash_request = SplashRequest(url=self.start_urls[0],
                                             callback=self.parse,
                                             args={"wait": 10},
                                             endpoint="render.html")
              # 4: 交付给引擎:
              yield splash_request
      
      
          def parse(self, response):
              with open("./with_splash.html", "w", encoding="utf-8") as f:
                  f.write(response.body.decode())
      
    • 然后增加配置信息:

      # 渲染的配置信息
      # 渲染服务的url
      SPLASH_URL = 'http://127.0.0.1:8050'
      # 下载器中间件
      DOWNLOADER_MIDDLEWARES = {
          'scrapy_splash.SplashCookiesMiddleware': 723,
          'scrapy_splash.SplashMiddleware': 725,
          'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
      }
      # 去重过滤器
      DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
      # 使用Splash的Http缓存
      HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
      

12.4: Scrapy-redis与Scrapy_splash配合使用:

  • redis与splash的配置冲突:指纹去重类:

    在配置方面,我们发现两个的配置,只有指纹去重类是冲突的,其余的并不冲突。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qaAKUClP-1613550120354)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612505808391.png)]

  • 我们splash的指纹去重:

    我们发现,splash的指纹去重实际上继承了redis的指纹去重,然后增加了splash自己特有的一些功能,两者最后都是返回指纹字符串。如果两者都配置,则会出现冲突。 因此我们的思路是,自己定义一个去重类,自己实现指纹去重。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qb590RHp-1613550120355)(C:\Users\11737\AppData\Roaming\Typora\typora-user-images\1612507257190.png)]

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奈何碎银没有几两

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值