一. 爬虫概述

一. 爬虫在使用场景中的分类

  1. 通用爬虫:

    ​ 抓取系统的重要组成部分,抓取的是一整张页面数据。

  2. 聚焦爬虫:

    ​ 建立在通用爬虫的基础之上的,抓取的是页面中特定的局部内容。

  3. 增量式爬虫:

    ​ 检测网站中数据更新的情况,只会爬取网站中最新更新出来的数据。

二. 爬虫的茅与盾

  1. 反爬机制

    ​ 门户网站,可以通过制定相关的策略或者技术手段,防止爬虫程序 进行网站数据的爬取。

  2. 反反爬策略

    ​ 爬虫程序可以通过制定相关的策略或者手段,破解门户网站的反爬机制。

三. robots.txt协议

​ 君子协议,规定了网站中哪些数据可以被爬虫爬取哪些数据不可以。

四. http协议

​ 概念:服务器和客户端进行数据交互的一种形式。

五. 常用请求头信息

   1. User-Agent : 请求载体的身份标识(浏览器)。

2. Connection :请求完毕后,是断开连接还是保持连接。

六. 常用响应头信息

1. Content-Type: 服务器响应客户端的数据类型。

七. https

​ 安全的超文本传输协议

八. 加密方式

1. 对称密钥加密
2. 非对称密钥加密
3. 证书密钥加密

二. requests(建议)

一. requests模块
  1. urllib模块:这个模块比较古老,对于爬虫的操作比较复杂。
  2. requests模块:简洁、高效。
    1. python中原生的一款基于网络请求的模块,功能强大,简单便捷,效率极高。

    2. 作用:模拟浏览器发请求。

    3. 如何使用:(requests模块的编码流程)

      1. 指定url
      2. 发起请求
      3. 获取响应数据
      4. 持久化存储
    4. 环境安装:

    ​ pip install requests

    1. 实战编码:

    ​ 需求:爬取搜狗首页页面数据:

    # -*- coding:utf-8 -*-
    # 需求:爬取搜狗首页的页面数据
    import requests
    if __name__ == '__main__':
        #1.指定url
        url = "https://www.sogou.com/"
        #2.发起请求,get方法会返回一个响应对象
        response = requests.get(url=url)
        #3.获取响应数据,text返回的是字符串形式的响应数据
        page_text = response.text
        print(page_text)
        #4.持久化存储
        with open('./sogou.html','w',encoding='utf-8') as fp:
            fp.write(page_text)
        print('爬取数据结束')
    
  3. 案例
    1. UA伪装:

      ​ UA:User-Agent (请求载体的唯一标识)

    2. 案例1:爬取搜狗指定词条对应的搜索结果页面(简易网页采集器)

      import requests
      if __name__ == "__main__":
          #UA检测:
          #UA伪装:将对应的User-Agent封装到一个字典中
          headers = {
              'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.183'
          }
          url = "https://www.sogou.com/web"
          #处理url携带的参数:封装到字典中
          kw = input('输入一个关键词:')
          param = {
              'query':kw
          }
          response = requests.get(url=url,params=param,headers=headers)
          page_text = response.text
          fileName = kw + '.html'
          with open(fileName,'w',encoding='utf-8') as fp:
              fp.write(page_text)
      
    3. 案例2:破解百度翻译

      1. post请求(携带了参数)
      2. 响应数据是一组json数据
      import requests
      import json
      if __name__ == "__main__":
          # 1.指定url
          post_url = 'https://fanyi.baidu.com/sug'
          # 2.进行UA伪装
          headers = {
              'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.183'
          }
          # 3.post请求参数处理(同get请求一致)
          word = input('输入一个词:')
          data = {
              'kw': word
          }
          # 4.请求发送
          response = requests.post(url=post_url,data=data,headers=headers)
          # 5.获取响应数据:.text返回的是一个字符串形式的json串
          # .json方法返回的是一个对象。(如果确认响应数据是json类型的,才可以使用json方法)
          # 响应头信息中可以确认类型
          dic_obj = response.json()
          print(dic_obj)
          # 6.持久化存储
          fileName = word+'.json'
          fp = open(fileName,'w',encoding='utf-8')
          json.dump(dic_obj,fp=fp,ensure_ascii=False)
      
          print('over!!')
      
    4. 案例3:爬取豆瓣电影排行榜 https://movie.douban.com/中的电影详细数据

三. 数据解析-bs4

1.聚焦爬虫:爬取页面中的指定的页面内容

​ 编码流程:

1. 指定url
2. 发起请求
3. 获取响应数据
4. 数据解析
5. 持久化存储
2.数据解析分类
  1. 正则
  2. bs4
  3. xpath(通用性强)
3.数据解析原理概述

​ 解析的局部的文本内容都会在标签之间或者标签对应的属性中进行存储

	1. 进行指定标签的定位
	2. 标签或者标签对应的属性中存储的数据值进行提取(解析)
4.常用正则表达式

	单字符:

​			. :  除换行以外所有字符

​			[] 	:  [aoe]、[a-w] 匹配集合中任意一个字符

​			\d    :数字 [0-9]

​			\D	 :  非数字

   	        \w    :  数字、字母、下划线、中文

​			\W    :  非\w

​			\s      :  所有的空白字符,包括空格、制表符、换行符等等,等价于[\f\n\r\t\v]

​			\S	   :  非空白

​	数量修饰:

​		*  :任意多次,>=0

​		+  :至少一次,>=1

​		?:可有可无,0次或者1次

​		{m}:固定m次

​		{m,}:至少m次

​		{m,n}:m-n次

​	边界:

​		$ :以某某结尾

​		^ :以某某开头

​	分组:(ab) 

​	贪婪模式:	.*

​	非贪婪:	.*?

​	re.I :	 忽略大小写

​	re.M :	多行匹配

​	re.S :	单行匹配

​	re.sub :(正则表达式,替换内容,字符串)
由这个正则表达式 ex = '<div class="thumb">.*?<img src="(. * ?)" alt.* ?</div>'可以提取到下面这个html的src里面的链接,基于正则的数据解析也可以用到其他语言中。
<div class="thumb">

<a href="/article/121721100" target="_blank">
<img src="//pic.qiushibaike.com/system/pictures/12172/121721100/medium/***.jpg" alt="***">
</a>

</div>
re.findall(ex,page_text,re.S)
5.bs4进行数据解析:只能用于python
一.数据解析的原理:

 	1. 标签定位
 	2. 提取标签、标签属性中存储的数据值

二.bs4数据解析的原理:

 	1. 实例化一个BeautifulSoup对象,并且将页面源码数据加载到该对象中
 	2. 通过调用BeautifulSoup对象中相关的属性或者方法进行标签定位和数据提取
三.环境安装:
	1.pip install bs4: 如果下载bs4失败,可能是下载源的问题,修改下载源。
	2.pip install lxml:  lxml解析器
- 需要将pip源设置为国内源,阿里源、豆瓣源、网易源等。
	- windows
    1.打开文件资源管理器(文件夹地址栏中)
    2.地址栏上面输入%appdata%
    3.在这里面新建一个文件夹pip
    4.在pip文件夹里面新建一个文件pip.ini,内容写如下即可
    [global]
    timeout = 6000
    index-url = https://mirrors.aliyun.com/pypi/simple/
    trusted-host = mirrors.aliyun.com
	-linux
	1.cd ~
	2.mkdir ~/.pip
	3.vi ~/.pip/pip.conf
	4.编辑内容和windows一样
四.如何实例BeautifulSoup对象:
	- from bs4 import BeautifulSoup
	-对象的实例化:
		1.将本地的html文档中的数据加载到该对象中,加载的同时进行了实例化
		   fp = open('./test.html','r',encoding='utf-8')
   		   BeautifulSoup(fp,'lxml')#构造方法,第一个参数文件描述符,第二个使用lxml解析器进行解析
   		2.将互联网上获取的页面源码加载到该对象中
           page_text = response.text
           soup = BeautifulSoup(page_text,'html')
五.提供的用于方法解析的方法和属性:
    	1.soup.tagName:返回第一次出现的tagName标签。
    	2.soup.find():
    		-find('tagName'):等同soup.tagName
    		-属性定位:
    			-soup.find('div',class_/id/attr='*')
    	3.soup.find_all():返回符合要求的所有标签(列表)
    	4.soup.select
    		-select('某种选择器(id,class,标签...选择器)'),返回的是一个列表
    		-层级选择器:
    			-soup.select('.tangf>ul>li>a'):>表示一个层级
    			-soup.select('.tangf>ul a'):空格表示多个层级
    	5.获取标签之间的文本数据:
    		- soup.a.text/string/get_text()
    		- text/get_text():可以获取某一个标签中所有的文本内容
    		- string:只可以获取该标签下面直系的文本内容
    	6.获取标签中属性值:
    		- soup.a['href']
6.bs4案例:
# -*- coding = utf-8 -*-
# @Time :2023/7/27 14:28
# 需求:爬取三国演义小说所有章节标题和章节内容
# https://www.shicimingju.com/book/sanguoyanyi.html
# bs4的实践
import requests
from bs4 import BeautifulSoup
if __name__ == '__main__':
    # 对首页的页面进行爬取
    url =  'https://www.shicimingju.com/book/sanguoyanyi.html'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.183'
    }
    # .text 返回的是Unicode型的数据,是由requests模块猜测的编码方式将内容编码成了Unicode,所以可能出现乱码。
    # .content 返回的是二进制数据,需要decode解码才能看。
    page_text = requests.get(url=url,headers=headers).content.decode('utf-8')
    # 1.实例化BeautifulSoup对象,需要将页面源码数据加载到该对象中
    soup = BeautifulSoup(page_text,'lxml')
    # 在首页中解析出章节的标题和详情页的url
    li_list = soup.select('.book-mulu > ul >li')
    fp = open('./sanguo.txt','w',encoding='utf-8')
    for li in li_list:
        title = li.a.string
        detail_url = 'https://www.shicimingju.com'+li.a['href']
        # 对详情页发起请求,解析出章节内容
        detail_page_text = requests.get(url=detail_url,headers=headers).content.decode('utf-8')
        # 解析出详情页中相关的内容
        detail_soup = BeautifulSoup(detail_page_text,'lxml')
        div_tag = detail_soup.find('div',class_='chapter_content')
        # 解析到章节内容
        # 去掉&nbsp;硬空格,必须在unicode下替换才行:
        # 这里比up主多了一步用.replace()方法将我们内容中的空格字符替换成了空格
        content = div_tag.text.replace(u'\xa0', '')
        fp.write(title+':'+content+'\n')
        print(title+'爬取成功!')

三. 数据解析-xpath(建议)

1.xpath解析:是最常用且最便捷高效的一种解析方式,通用性。
2.xpath解析原理:
1.实例化一个etree的对象,且需要将被解析的页面源码数据加载到该对象中。
2.调用etree对象中的xpath方法结合着xpath表达式实现标签的定位和内容的捕获。
3.环境的安装:

​ pip install lxml

4.如何实例化一个etree对象:from lxml import etree,etree是一个类。
1.将本地的html文档中的源码数据加载到etree对象中
	etree.parse(filePath)
2.来自互联网
	etree.HTML('page_text')
- xpath('xpath表达式')
- 从本地读一个html
from lxml import etree
if __name__ == '__main__':
    # 实例化一个etree对象,且将被解析的源码加载到该对象。
    tree = etree.parse('sogou.html',etree.HTMLParser())
    r = tree.xpath('/html/head/title')
    print(r)
5.xpath表达式:
1./ :表示的是从根节点开始定位,表示的是一个层级。
2.// :表示的是多个层级,可以表示从任意位置开始定位。
3.属性定位://div[@class='song'], tag[@attrName="attrValue"]
4.索引定位://div[@class='song']/p[3],div标签下的第三个p标签,索引是从1开始的。
5.取文本:
	- /text():获取的是标签中直系的文本内容
	- //text():标签中非直系的文本内容(所有的文本内容)
6.取属性:
	/@attrName    ==> img/src
6.xpath案例:
案例1.爬取安居客上的二手房信息的标题
# https://guangzhou.anjuke.com/sale/?from=HomePage_TopBar 安居客
import requests
from lxml import etree
if __name__ == '__main__':
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.183'
    }
    url = 'https://guangzhou.anjuke.com/sale/?from=HomePage_TopBar'
    page_text = requests.get(url=url,headers=headers).text
    tree = etree.HTML(page_text)
    #存储的div对象
    div_list = tree.xpath('//section[@class="list"][1]/div')
    fp = open('58.txt','w',encoding='utf-8')
    for div in div_list:
        title = div.xpath('./a//div[@class="property-content-title"][1]/h3/text()')[0]#./表示div标签,当前标签。
        print(title)
        fp.write(title+'\n')
案例2.爬取4k图片
# 需求:解析下载图片数据,https://pic.netbian.com/4kmeinv/
import os.path
import requests
from lxml import etree
if __name__ == "__main__":
    url = 'https://pic.netbian.com/4kmeinv/'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.183'
    }
    # 1.这里.content.decode('utf-8')解决不了乱码问题,不指定格式即可。
    response = requests.get(url=url,headers=headers)
    # 2.手动设定响应数据的编码格式
    # response.encoding = 'utf-8'
    page_text = response.content
    # 数据解析src属性值,alt属性值
    tree = etree.HTML(page_text)
    li_list = tree.xpath('//ul[@class="clearfix"]/li')
    # 创建一个文件夹
    if not os.path.exists('./picLibs'):
        os.mkdir('./picLibs')

    for li in li_list:
        img_src = 'https://pic.netbian.com'+li.xpath('.//img/@src')[0]
        img_name = li.xpath('.//img/@alt')[0]+'.jpg'
        # 3.通用处理中文乱码的解决方案,只对乱码的部分进行了编码再解码
        # res.encoding = res.apparent_encoding
        # 从内容中分析出的响应内容编码
        # img_name = img_name.encode('iso-8859-1').decode('gbk')
        # 请求图片进行持久化存储
        img_data = requests.get(url=img_src,headers=headers).content
        img_path = 'picLibs/'+img_name
        with open(img_path,'wb') as fp:
            fp.write(img_data)
            print(img_name,'下载成功!!!')
案例3.爬取城市名称,不同标签下同一a标签的运用
# -*- coding = utf-8 -*-
# @Time :2023/7/30 21:31
# @Author:
# @File : demo11_xpath实践3.py
# @Software: PyCharm
# 需求:解析出所有城市名称https://www.aqistudy.cn/historydata/
import os.path
import requests
from lxml import etree
if __name__ == "__main__":
    # 例1. url = 'https://pic.netbian.com/4kmeinv/'
    # headers = {
    #     'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.183'
    # }
    # url = 'https://www.aqistudy.cn/historydata/'
    # page_text = requests.get(url=url,headers=headers).text
    # tree = etree.HTML(page_text)
    # all_city_names = []
    # hot_li_list = tree.xpath('//ul[@class="unstyled"]/li')
    # # 热门城市
    # for li in hot_li_list:
    #     hot_city_name = li.xpath('./a/text()')[0] # 0表示返回的列表中只有一个列表元素
    #     all_city_names.append(hot_city_name)
    #
    # # 解析所有城市
    # city_name_list = tree.xpath('//div[@class="bottom"]/ul/div[2]/li')
    # for li in city_name_list:
    #     city_name = li.xpath('./a/text()')[0]
    #     all_city_names.append(city_name)
    # print(all_city_names,len(all_city_names))
    # 例2.
    url = 'https://pic.netbian.com/4kmeinv/'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.183'
    }
    url = 'https://www.aqistudy.cn/historydata/'
    page_text = requests.get(url=url, headers=headers).text
    tree = etree.HTML(page_text)
    # 解析到热门城市和所有城市对应的a标签
    # //div[@class="bottom"]/ul/li/a            热门城市a标签的层级关系
    # //div[@class="bottom"]/ul/div[2]/li/a     全部城市a标签的层级关系
    a_list = tree.xpath('//div[@class="bottom"]/ul/li/a | //div[@class="bottom"]/ul/div[2]/li/a')
    all_city_names = []
    for a in a_list:
        city_name = a.xpath('./text()')[0]
        all_city_names.append(city_name)
    print(all_city_names,len(all_city_names))



练习1.
# 爬取站长素材中的免费ppt模板 https://sc.chinaz.com/
# https://sc.chinaz.com/jianli/free.html
# https://sc.chinaz.com/ppt/free.html
import os.path
import requests
from lxml import etree
if __name__ == "__main__":
    base_url = 'https://sc.chinaz.com/ppt/free.html'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.183'
    }
    if not os.path.exists('./ppt'):
        os.mkdir('./ppt')
    for i in range(1,2):
        response = requests.get(url=base_url,headers=headers)
        response.encoding = response.apparent_encoding
        ful_page = response.text
        # print(ful_page)
        base_url = 'https://sc.chinaz.com/ppt/'+'free_'+str(i)+'.html'
        tree = etree.HTML(ful_page)
        div_list = tree.xpath('//div[@class="ppt-list "]/div')
        # print(div_list)
        for div in div_list:
            detail_url ='https://sc.chinaz.com/'+ div.xpath('./div[2]/a/@href')[0]
            ppt_name = div.xpath('./div[2]/a/text()')[0]+'.rar'
            # print(detail_url,ppt_name)
            # 详情页
            detail_page = requests.get(url=detail_url,headers=headers).text
            tree_detail = etree.HTML(detail_page)
            # 下载url
            load_url = tree_detail.xpath('//div[@class="download-url"]/a[1]/@href')[0]
            ppt_data = requests.get(url=load_url,headers=headers)
            print(load_url,ppt_data.status_code)
            ppt_path = 'ppt/' + ppt_name
            with open(ppt_path, 'wb') as fp:
                fp.write(ppt_data.content)
                print(ppt_name, '下载成功!!!')
上面这个练习,由于页面源码和返回的数据不一样,数据在不同的标签中,现在不知道什么原因,我采取的是爬返回数据中标签的数据。

四.验证码识别简介

1.反爬机制:验证码。识别验证码中的数据,用于模拟登录操作。
2.识别验证码的操作:
1. 肉眼识别
2. 第三方自动识别(建议)
	-云打码:已跑路,其他平台流程应该差不多。
3.云打码使用流程:
	注册:普通用户和开发者用户
	登录:
		普通用户登录用于查询是否有分
		开发者用户登录:
			创建一个软件:id和密钥
			下载示例代码:
实战:识别古诗文网登录页面中的验证码
1.将验证码下载到本地
2.调用平台提供的实例代码进行图片识别
from hashlib import md5
import requests
from lxml import etree
# 封装识别验证码图片的函数
def getCodeText(userName, password, appId, imgUrl):
    class Chaojiying_Client(object):

        def __init__(self, username, password, soft_id):
            self.username = username
            password = password.encode('utf8')

            self.password = md5(password).hexdigest()
            self.soft_id = soft_id
            self.base_params = {
                'user': self.username,
                'pass2': self.password,
                'softid': self.soft_id,
            }
            self.headers = {
                'Connection': 'Keep-Alive',
                'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
            }

        def PostPic(self, im, codetype):
            """
            im: 图片字节
            codetype: 题目类型 参考 http://www.chaojiying.com/price.html
            """
            params = {
                'codetype': codetype,
            }
            params.update(self.base_params)
            files = {'userfile': ('ccc.jpg', im)}
            r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files,
                              headers=self.headers)
            return r.json()

        def ReportError(self, im_id):
            """
            im_id:报错题目的图片ID
            """
            params = {
                'id': im_id,
            }
            params.update(self.base_params)
            r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
            return r.json()

    if __name__ == '__main__':
        chaojiying = Chaojiying_Client(userName, password, appId)  # 用户中心>>软件ID 生成一个替换 96001
        im = open(imgUrl, 'rb').read()  # 本地图片文件路径 来替换 a.jpg 有时WIN系统须要//
        # print(chaojiying.PostPic(im, 1902))  # 1902 验证码类型  官方网站>>价格体系 3.4+版 print 后要加()
    return chaojiying.PostPic(im, 1004)

headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.183'
    }
url = 'https://so.gushiwen.cn/user/login.aspx?from=http://so.gushiwen.cn/user/collect.aspx'
page_text = requests.get(url=url,headers=headers).text
tree = etree.HTML(page_text)
code_img_src = 'https://so.gushiwen.cn' + tree.xpath('//*[@id="imgCode"]/@src')[0]
img_data = requests.get(url=code_img_src,headers=headers).content
# 将验证码保存到本地
with open('./code.jpg','wb') as fp:
    fp.write(img_data)

# 调用打码平台的示例程序进行验证码图片数据识别
result = getCodeText('hongye','HongYe9+', '951259', 'code.jpg')
print('识别结果为:',result)
3.模拟登录
# -*- coding = utf-8 -*-
# @Time :2023/8/2 16:16
# @Author:
# @File : demo14_模拟登录.py
# @Software: PyCharm
# 模拟登录古诗文网
import requests
from lxml import etree
from CodeClass import Chaojiying_Client
session = requests.Session()
def getCode():
    chaojiying = Chaojiying_Client('hongye', 'HongYe9+', '951259')  # 用户中心>>软件ID 生成一个替换 96001
    im = open('code1.jpg', 'rb').read()  # 本地图片文件路径 来替换 a.jpg 有时WIN系统须要//
    return chaojiying.PostPic(im, 1004)
# 1.对验证码图片进行捕获和识别
headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36'
    }
url = 'https://so.gushiwen.cn/user/login.aspx'
page_text = session.get(url=url,headers=headers).text
tree = etree.HTML(page_text)
code_img_src = 'https://so.gushiwen.cn' + tree.xpath('//*[@id="imgCode"]/@src')[0]
view_state = tree.xpath('//*[@id="__VIEWSTATE"]/@value')[0] # *表示任意的标签
img_data = session.get(url=code_img_src,headers=headers).content
# 将验证码保存到本地
with open('./code1.jpg','wb') as fp:
    fp.write(img_data)
result = getCode()
code = result['pic_str']
print(result,code)
login_url = 'https://so.gushiwen.cn/user/login.aspx'
data = {
'__VIEWSTATE': view_state,
'__VIEWSTATEGENERATOR': 'C93BE1AE',
'from': 'http://so.gushiwen.cn/user/collect.aspx',
'email': '2456335130@qq.com',
'pwd': 'HongYe9+',
'code': code,
'denglu': '登录'
}
response = session.post(url=login_url,headers=headers,data=data)
# print(response.status_code)
login_page = response.text
with open('gushiwen.html','w',encoding='utf-8') as fp:
    fp.write(login_page)
4.模拟登录cookie操作
进行模拟登录之后,可以继续发请求进行个人主页。但是服务器不会对用户的登录状态记录,所以不会成功
1.http/https协议特性:无状态
2.cookie:用来让服务器端记录客户端的相关状态。

解决:
1.手动处理:通过抓包工具获取cookies(即浏览器的请求头里面),手动在headers中携带cookies,cookie有时效性,不通用。
2.自动处理:(建议)
	cookie值得来源:登录后由服务器创建
	session会话对象:
		-作用:1.可以进行请求的发送
			  2.如果请求过程中产生了cookie,则该cookie会自动存储/携带在该session对象中。
	流程:
	1.创建一个session对象:session = requests.Session()
	2.使用session对象进行模拟登录post请求的发送(cookie就会被存储在session中)
	3.session对象对个人主页对应的get请求进行发送(携带了cookie)

五.代理

1.现象:403、ip访问频率太高…
2.代理:破解封ip机制
1.什么是代理:
	-代理服务器
2.代理的作用:
	-突破自身ip访问的限制
	-隐藏自身ip
3.代理相关的网站:
	-快代理
	-西祠代理
	-www.goubanjia.com
	-易代理
4.代理ip的类型:
	-http:应用到http协议对应的url中
	-https:应用到https协议对应的url中
5.代理ip的匿名度:
	-透明:服务器知道该次请求使用了代理服务器,也知道请求对应的真实ip
	-匿名:知道代理,不知道真是ip
	-高匿:都不知道
6.使用:
	在请求中增加一个参数,get(url=,header=,proxies={})
	proxies={"对应协议":"对应ip"},proxies={"http":"192.168.10.125"}

六.高性能的异步爬虫

1.目的:在爬虫中使用异步实现高性能的数据爬取操作
2.异步爬虫的方式:
1.多线程,多进程:(不建议)
	优:可以为相关阻塞的操作单独开启线程或者进程,阻塞操作可以异步执行。
	缺:无法无限制的开启多线程或者多进程,占资源。
2.线程池、进程池:(适当)
	优:我们可以降低系统对进程或者线程创建和销毁的一个频率,从而很好地降低系统的开销。
	缺:池中线程或者进程的数量是有上限的。

3.线程池的使用
# -*- coding = utf-8 -*-
# @Time :2023/8/3 17:37
# @Author:
# @File : demo15_线程池的基本使用.py
# @Software: PyCharm
# import time
# # 使用单线程串行方式执行
# # 模拟发送请求
# def get_page(str):
#     print("正在下载:",str)
#     time.sleep(2)
#     print('下载成功:',str)
#
# name_list = ['xiaozi','aa','bb','cc']# 模拟url
#
# start_time = time.time()
#
# for i in range(len(name_list)):
#     get_page(name_list[i])
#
# end_time =time.time()
# print('%d second'%(end_time-start_time))


import time
# 导入线程池对应的类
from multiprocessing.dummy import Pool
# 使用线程池方式执行
# 模拟发送请求
start_time = time.time()
def get_page(str):
    print("正在下载:",str)
    time.sleep(2)
    print('下载成功:',str)

name_list = ['xiaozi','aa','bb','cc']# 模拟url

# 实例化一个线程池对象
pool = Pool(4)
# 将列表中每一个列表元素传递给get_page进行处理
pool.map(get_page,name_list)#map函数的第一个是阻塞的方法,第二个参数是要处理的url

end_time =time.time()
print('%d second'%(end_time-start_time))
4.异步爬虫之线程池案例应用

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

我们进入万象这个主题的任意一个视频的详情页,打开抓包工具发现,页面元素src中对应的链接并不是该视频真正的链接,伪链接:https://video.pearvideo.com/mp4/short/20230807/cont-1785670-71098575-hd.mp4,我们点击抓包工具的->网络->搜索->mp4发现在一个videoStatus.jsp的ajax请求的响应体中存在这个关键字,srcUrl对应的值:https://video.pearvideo.com/mp4/short/20230807/1691391120481-71098575-hd.mp4拿去访问可以访问到详情页对应的视频,所以这才是真的视频链接,我们发现这个请求https://www.pearvideo.com/videoStatus.jsp?contId=1785670&mrd=0.06169182147693242有两个参数,contId是视频的id,可以在主题页得到,第二个是什么呢?在抓包工具中搜索,发现这是一个由js生成的一个随机数,所以按道理我们只需要https://www.pearvideo.com/videoStatus.jsp?contId=1785670链接即可访问到该页面并得到json数据,但是出现文章已下线,这是因为服务器进行了防盗处理:我们只需要在headers中加个Referer :此内容用来标识这个请求是从哪个页面发过来的,服务器可以拿到这一信息并做相应的处理,如做来源统计、防盗处理等。因此在headers中增加 'Referer': detail_url 其中detail_url为 形如:https://www.pearvideo.com/video_1720499 这样的链接,即:该视频的详细页链接 用来表示访问https://www.pearvideo.com/videoStatus.jsp?contId=1720499这个请求是由https://www.pearvideo.com/video_1720499 这个网页发过来的。我们共发了三次请求,第一次访问主题页万象,第二次访问https://www.pearvideo.com/videoStatus.jsp?contId=1720499得到json数据,第三次访问视频真正的链接并下载视频。
# -*- coding = utf-8 -*-
# @Time :2023/8/4 15:22
# @Author:
# @File : demo01_异步爬虫之线程池案例.py
# @Software: PyCharm
# https://video.pearvideo.com/mp4/short/20230807/cont-1785670-71098575-hd.mp4
# https://video.pearvideo.com/mp4/short/20230807/1691391120481-71098575-hd.mp4
import time
import requests
from lxml import etree
import re
from multiprocessing.dummy import Pool
session = requests.Session()
url = 'https://www.pearvideo.com/panorama'
headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
    }
page_text = session.get(url=url,headers=headers).text
tree = etree.HTML(page_text)
li_list = tree.xpath('//ul[@class="listvideo-list clearfix"]/li | //ul[@class="category-list clearfix"]/li')
urls = []# 存储所有视频的链接和名字
for li in li_list:
    a_href = li.xpath('./div/a/@href')[0]
    contId = a_href[6:]
    detail_url = 'https://www.pearvideo.com/'+a_href
    name = li.xpath('./div/a/div[2]/text()')[0]+'.mp4'
    headers_referer = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
        'Referer': detail_url
    }
    down_url = 'https://www.pearvideo.com/videoStatus.jsp?contId='+ contId
    # 详情页中发送ajax请求获得json数据
    detail_json = session.get(url=down_url,headers=headers_referer).json()
    mp4_url = detail_json['videoInfo']['videos']['srcUrl']
    mp4_url = re.sub('/(\d*?)-', '/cont-' + contId + '-', mp4_url)
    print(mp4_url)
    dic = {
        'name':name,
        'url':mp4_url
    }
    urls.append(dic)



def get_videoUrl(urls):
    name = ''.join(urls['name'].split('|'))  # 对标题进行重组,以便视频存储
    url = urls['url']
    data = requests.get(url,headers=headers).content
    print(name,'开始下载')
    with open(name, 'wb') as fp:
        fp.write(data)
        fp.close()
    print(name,'下载成功')

# 使用线程池对视频数据进行请求
StartTime = time.time()
pool = Pool(4)
pool.map(get_videoUrl, urls)
EndTime = time.time()
print('运行结束,耗时{}s'.format(EndTime-StartTime))
pool.close() # 关闭线程池
pool.join()  # 主线程等待子线程结束后加入

5.单线程+异步协程(推荐)
event_loop:事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上,当满足某些条件的时候,函数就会被循环执行。

coroutine:协程对象,我们可以将协程对象注册到事件循环中,它会被事件循环调用。我们可以使用async关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回一个协程对象。

task:任务,它是对协程对象的进一步封装,包含了任务的各个状态。

future:代表将来执行或还没执行的任务,实际上和task没有本质区别。

async:定义一个协程。

await:用来挂起阻塞方法的执行。

基于python3.6的协程
6.协程基本操作
# -*- coding = utf-8 -*-
# @Time :2023/8/9 17:06
# @Author:
# @File : demo02_协程基于3.6的.py
# @Software: PyCharm
import asyncio

async def request(url):
    print('正在请求的url是',url)
    print('请求成功,',url)

# async修饰的函数调用之后返回的一个协程对象(async修饰的函数当调用时并不会直接执行这个函数)
c = request('www.baidu.com')

# 创建一个事件循环对象
loop = asyncio.get_event_loop()
# 将协程对象注册到loop中,然后启动loop
loop.run_until_complete(c)
7.task、future对象的使用
# -*- coding = utf-8 -*-
# @Time :2023/8/9 17:16
# @Author:
# @File : demo03_task对象的使用.py
# @Software: PyCharm
import asyncio

async def request(url):
    print('正在请求的url是',url)
    print('请求成功,',url)
    return url

c = request('www.baidu.com')
# # 1.task的使用
# # 创建事件循环对象
# loop = asyncio.get_event_loop()
# # 基于loop创建了一个task对象
# task = loop.create_task(c)
# print(task)
#
# loop.run_until_complete(task)
# print(task)

# 2.future的使用
# loop = asyncio.get_event_loop()
#
# future = asyncio.ensure_future(c)
# print(future)
# loop.run_until_complete(future)
# print(future)

# 3.回调函数
def callback_func(task):
    # result返回的就是任务对象中封装的协程对象对应函数的返回值
    print(task.result)

# 绑定回调
loop = asyncio.get_event_loop()
future = asyncio.ensure_future(c)
# 将回调函数绑定到任务对象中
future.add_done_callback(callback_func)
loop.run_until_complete(future)
8.多任务异步协程
# -*- coding = utf-8 -*-
# @Time :2023/8/9 17:44
# @Author:
# @File : demo04_多任务协程.py
# @Software: PyCharm
import asyncio
import time

async def request(url):
    print('正在下载',url)
    # 在异步协程中如果出现了同步模块相关的代码,那么就无法实现异步。time.sleep(2)为同步代码
    # time.sleep(2)
    # 当在asyncio中遇到阻塞操作必须进行手动挂起await,async和await是python3.6出的
    await asyncio.sleep(2)
    print('下载完毕',url)

start =time.time()
urls = [
    'www.baidu.com',
    'www.sogou.com',
    'www.goubanjia.com'
]

# 任务列表:存放多个任务对象
tasks = []
for url in urls:
    c = request(url)
    task = asyncio.ensure_future(c)
    tasks.append(task)

loop = asyncio.get_event_loop()
# 需要将任务列表封装到wait中,不能直接使用tasks
loop.run_until_complete(asyncio.wait(tasks))
print(time.time()-start)
9.aiohttp模块的引出
1.requests.get是基于同步,必须使用基于异步的网络请求模块进行指定url的请求发送
2.aiohttp:基于异步网络请求的模块

pip被卸载后安装回pip:python -m ensurepip
	安全更新pip:
	1.python -m pip install --upgrade pip

	写全python命令。

	这个的原理尚不明确,但是确实好使,我之前一直是这么弄得。我个人感觉	这是从python的全局角度进行更新pip,而不是pip单独去更新pip,所以可	以成功。

	2.pip install --upgrade --user pip

	上边第二张图那里也看到了,更新失败的原因是没有权限,所以给上用户权	限即可。就是加上--user

3.环境安装
	1.pip install aiohttp
	
4.使用该模块的session对象(是基于ClientSession类)

5.具体操作就是把发送请求替换成:
   async with aiohttp.ClientSession() as session:
    #get请求有一定的耗时,将耗时阻塞的方法用await进行挂起
   	async with await session.get(url) as response:
    #text()返回字符串形式的响应数据
    #read()返回的二进制的响应数据
    #json()返回的就是json对象
    #注意:在获取响应数据操作之前一定要使用awiat进行手动挂起,否则拿不到对应结果。
    	page_text = await response.text()

selenium模块

问题:

selenium模块和爬虫之间具有怎样的关联?

1.便捷的获取网站中动态加载的数据

2.便捷实现模拟登录

1.selenium的简介

1.基于浏览器自动化的一个模块

2.selenium模块的使用流程
1.环境安装:pip install selenium
2.下载浏览器的驱动程序(谷歌链接):
	https://chromedriver.storage.googleapis.com/index.html

查看驱动和浏览器版本的映射关系:
https://blog.csdn.net/huilan_same/article/details/51896672

谷歌浏览器及驱动下载地址:
https://googlechromelabs.github.io/chrome-for-testing/#stable

3.实例化一个浏览器对象:

4.编写基于浏览器自动化的操作代码
executable_path已被弃用,只用驱动程序与python.exe在同级目录下就可以直接调用,不需要指明路径。
from selenium import webdriver
bro = webdriver.Chrome()
bro.get('https://baidu.com')
用谷歌浏览器访问百度
5.其他操作:
	1.发起请求:get(url)
	2.标签定位:find系列函数
	3.标签交互:send_keys('xxx')
	4.执行js程序:excute_script('jscode')
	5.前进、后退:back(),forward()
	6.关闭浏览器:quit()
	代码:from selenium import webdriver
from time import sleep
bro = webdriver.Chrome()
bro.get('https://www.taobao.com/')
# 标签定位
search_input = bro.find_element_by_id('q')
# 标签交互
search_input.send_keys('iphone')
# 点击搜索按钮
btn = bro.find_element_by_css_selector('.btn-search')
btn.click()
sleep(2)
# 执行一组js程序
bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
sleep(2)
bro.get('https://www.baidu.com')
sleep(2)
# 回退
bro.back()
sleep(2)
# 前进
bro.forward()
# 等待2秒
sleep(2)
# 关闭浏览器
bro.quit()

6.selenium处理iframe
	iframe:内联框架,理解,一个页面中嵌套一个子页面。
	注意:如果定位的标签是存在于iframe标签之中的则必须通过如下操作再进行标签定位。
	bro.switch_to.frame('iframeResult'),切换浏览器标签定位的作用域,默认作用域存在于外部大的html中。
	
7.动作链

动作链:一系列动作的集合

在这里插入图片描述

8.模拟登录qq空间
# -*- coding = utf-8 -*-
from selenium import webdriver
from time import sleep
# 得到浏览器对象
bro = webdriver.Chrome()
# 发送请求
bro.get('https://qzone.qq.com/')
# 更改浏览器标签定位的作用域
bro.switch_to.frame('login_frame')
select_tag = bro.find_element_by_id('switcher_plogin')
select_tag.click()

account = bro.find_element_by_id('u')
password = bro.find_element_by_id('p')
account.send_keys('2456335130@qq.com')
password.send_keys('HongYe9+')
# 获取登录标签
login_tag = bro.find_element_by_id('login_button')
# 点击登录标签
login_tag.click()
sleep(3)
bro.quit()

9.谷歌无头浏览器
# -*- coding = utf-8 -*-
from selenium import webdriver
from time import sleep
# 实现无可视化
from selenium.webdriver.chrome.options import Options
# 实现规避检测
from selenium.webdriver import ChromeOptions
# 创建一个参数对象,用来控制chrome以界面模式打开
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')

# 如何实现selenium规避被检测识别
option = ChromeOptions()
option.add_experimental_option('excludeSwitches',['enable-automation'])

bro = webdriver.Chrome(chrome_options=chrome_options)
bro.get('https://www.baidu.com')

print(bro.page_source)
sleep(3)
bro.quit()
10.12306模拟登录
重点:
1.selenium能截图,bro.save_screenshot('a.png'),对当前网页截图并保存。
2.code_img_ele=bro.find_element_by_xpath('xx'),拿到对应网页中验证码的标签。
3.location = code_img_ele.location,得到验证码标签左上角的坐标x,y
4.size = code_img_ele.size,得到验证码标签对应的长和宽。
5.左上角和右下角坐标location和size都是一个字典,rangle = (int(location['x']),int(location['y']),int(location['x']+size['width']),int(location['y']+size['height']))
6.from PIL import Image,导入Image类对图片进行裁剪
7.i = Image.open('./a.png')
  code_img_name = './code.png'
  #按指定区域裁剪
  frame = i.crop(rangle)
  frame.save(code_img_name)
11.scrapy框架
一.scrapy框架的使用流程:
	1.环境的安装:pip install scrapy
	2.创建一个工程:(在终端Terminal操作)
	  	-scrapy startproject xxxPro
	  	-cd xxxPro
	  	-在spiders子目录中创建一个爬虫文件:
	  	scrapy genspider [spiderName] www.xxx.com
	  	-执行工程:
	  	scrapy crawl spiderName
二.scrapy数据解析
三.scrapy持久化存储
	1.基于终端指令:
	-只能存在本地文件,不能存到数据库,只可以将parse方法的返回值存储到本地文件中,且格式有限。
	-指令:scrapy crawl xxx -o filepath 
import scrapy

class GushiSpider(scrapy.Spider):
    name = "gushi"
    # allowed_domains = ["www.xxx.com"]
    start_urls = ["https://www.gushiwen.cn/"]

    def parse(self, response):
        div_list = response.xpath('//div[@class="left"]/div[@class="sons"]')
        all_data = []
        for div in div_list:
            # xpath返回的是列表,但是列表元素一定是Selector类型的对象
            # extract可以将Selector对象中data参数存储的字符串提取出来
            # 列表调用extract得到的是列表,extract_first()得到的是列表中的第一个列表元素对应的Selector使用extract操作
            # 确定返回的列表只有一个列表元素时才能使用extract_first()
            # title = div.xpath('./div[@class="cont"]/div[2]/p[1]/a[1]/b/text()')[0].extract()
            title = div.xpath('./div[@class="cont"]/div[2]/p[1]/a[1]/b/text()').extract_first()
            content = div.xpath('./div[@class="cont"]/div[2]/div[@class="contson"]//text()').extract()
            content = ''.join(content) # 将列表中的数据转成字符串
            dic = {
                'title':title,
                'content':content
            }
            all_data.append(dic)

        return all_data
	2.基于管道:
		-编码流程:
			-数据解析
			-在item类中定义相关的属性。
			-将解析的数据封装存储到item类型的对象。
			-将item类型的对象提交给管道进行持久化存储。
			-在管道类的process_item中将接收到的item对象中存储的数据进行持久化存储操作。
			-在配置文件中开启管道
			
	注意:爬虫文件提交的item只会给管道文件中第一个被执行的管道类接收,有两个管道类时,先执行的管道类一定要return item,否则下一个执行的管道类接收不到item。
			
# 1.爬虫文件
import scrapy
from ..items import GushiproItem
class GushiSpider(scrapy.Spider):
    name = "gushi"
    # allowed_domains = ["www.xxx.com"]
    start_urls = ["https://www.gushiwen.cn/"]
        def parse(self, response):
        div_list = response.xpath('//div[@class="left"]/div[@class="sons"]')
        all_data = []
        for div in div_list:
            # xpath返回的是列表,但是列表元素一定是Selector类型的对象
            # extract可以将Selector对象中data参数存储的字符串提取出来
            # 列表调用extract得到的是列表,extract_first()得到的是列表中的第一个列表元素对应的Selector使用extract操作
            # 确定返回的列表只有一个列表元素时才能使用extract_first()
            # title = div.xpath('./div[@class="cont"]/div[2]/p[1]/a[1]/b/text()')[0].extract()
            title = div.xpath('./div[@class="cont"]/div[2]/p[1]/a[1]/b/text()').extract_first()
            content = div.xpath('./div[@class="cont"]/div[2]/div[@class="contson"]//text()').extract()
            content = ''.join(content) # 将列表中的数据转成字符串

            item = GushiproItem()
            item['title'] = title
            item['content'] = content

            yield item# 将item提交给管道
# 2.item类
import scrapy


class GushiproItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title = scrapy.Field()
    content = scrapy.Field()
    # pass
# 3.管道类
class GushiproPipeline:
    fp = None
    # 重写父类的一个方法:该方法只在开始爬虫的时候被调用一次,打开文件在这里处理就只会打开一次
    def open_spider(self,spider):
        print("开始爬虫")
        self.fp = open('./gushi.txt','w',encoding='utf-8')
    # 专门用来处理item类型对象
    # 该方法可以接收到爬虫文件提交过来的item对象
    # 该方法每接收到一个item就会被调用一次
    def process_item(self, item, spider):
        title = item['title']
        content = item['content']
        self.fp.write(title+':'+content+'\n')

        return item
    # 重写父类方法,该方法只会在爬虫结束时调用一次
    def close_spider(self,spider):
        print('结束爬虫')
        self.fp.close()

12.基于scrapy框架,将爬取的数据存储到mysql
# 1.在管道文件中新增这个管道类
# 2.在配置文件中配置该管道类
#注意:爬虫文件提交的item只会给管道文件中第一个被执行的管道类接收,有#两个管道类时,先执行的管道类一定要return item,否则下一个执行的管道#类接收不到item。
# 管道文件中的一个管道类对应将一组数据存储到一个平台或者载体中
class mysqlPileLine(object):
    conn = None
    def open_spider(self, spider):
        # 实例化一个链接对象
        self.conn = pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='123456',db='gushi',charset='utf8')
    def process_item(self, item, spider):
        # 通过链接对象实例化一个游标对象,用来执行一组sql语句
        self.cursor = self.conn.cursor()

        try:
            self.cursor.execute('insert into tb_gushi values ("%s","%s")'%(item['title'],item['content']))
            self.conn.commit()
        except Exception as e:
            print(e)
            self.conn.rollback()
        return item
    def close_spider(self, spider):
        self.cursor.close()
        self.conn.close()
13.全站数据爬取:
全站数据爬取:就是将网站中某模块下的全部页码对应的页面数据进行爬取
需求:爬取校花网站中的照片的名称
实现方式:手动请求发送
import scrapy


class XiaohuaSpider(scrapy.Spider):
    name = "xiaohua"
    # allowed_domains = ["www.xxx.com"]
    start_urls = ["http://www.university-china.com/common-picture-list.php?pictureCategoryId=7"]
    # 生成一个通用的url模板
    url = 'http://www.university-china.com/common-picture-list.php?pictureCategoryId=7&pageNumber=%d'
    page_num = 2
    def parse(self, response):
        li_list = response.xpath('//div[@class="picnews-list margintop"]/dl/dd')
        for li in li_list:
            img_name = li.xpath('./a[1]/@title').extract()
            print(img_name)

        if self.page_num <=2:
            new_url = format(self.url%self.page_num)
            self.page_num +=1
            # 手动发送请求:callback回调函数是专门用作于数据解析
            yield scrapy.Request(url=new_url,callback=self.parse)


14.请求传参
1.使用场景:如果爬取的数据不在同一张页面中。(深度爬取)
2.需求:爬取boss的岗位名称,岗位描述。
import scrapy
from ..items import BossproItem

class BossSpider(scrapy.Spider):
    name = "boss"
    # allowed_domains = ["www.xxx.com"]
    start_urls = ["https://www.zhipin.com/web/geek/job?query=java&city=101280100&position="]
    # 通用url模板
    url = 'https://www.zhipin.com/web/geek/job?query=java&city=101280100&position=&page=%d'
    page_num = 2
    # 解析详情页
    def parse_detail(self,response):
        job_desc = response.xpath('').extract()
        job_desc = ''.join(job_desc)
        # 回调函数接收item
        item = response.meta['item']
        item['job_desc'] =job_desc
        yield item

    # 解析首页
    def parse(self, response):
        li_list = response.xpath('//div[@class="search-job-result"]/ul[@class="job-list-box"]/li')
        print(li_list)
        item = BossproItem()
        for li in li_list:
            job_name = li.xpath('.//div[@class="job-title clearfix"]/span[@class="job-name"]/text()').extract_first()
            item['job_name'] = job_name
            detail_url = li.xpath('')
            # 对详情页发请求(手动)
            # 请求传参:meta={},可以将meta字典传递给请求对应的回调函数
            yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item})

        if self.page_num<=3:
            new_url = format(self.url%self.page_num)
            self.page_num += 1
            yield scrapy.Request(new_url,callback=self.parse)
            
理解:当我们解析的数据在两个不同的方法中时,我们需要将两个parse方法中的数据都存在item对象中时,就使用请求传参。在第一个parse方法中将数据存进item,然后调用回调函数时将item(像这样:yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item}))传递给parse_detail方法,再在parse_detail方法中存数据到item并提交item给管道。
15.scrapy图片爬取
1.基于scrapy爬取字符串类型的数据和爬取图片类型的数据的区别
	-字符串:基于xpath进行解析并提交管道进行持久化存储
	-图片:xpath解析出图片src属性值,单独对图片地址发送请求获取图片		二进制类型数据。
2.ImagesPipeline:
	-只需要将img的src属性值进行解析,提交给管道,管道就会对图片的			src进行请求发送获取二进制类型的数据并持久化存储。
3.ImagesPipeline使用流程:
	-数据解析(图片地址)
	-将存储图片地址的item提交到制定的管道类
	-在管道文件中自制一个基于ImagesPipeline的一个管道类
		-重写get_media_requests、file_path、item_completed三				个方法
	-在配置文件中:
		-指定图片存储目录:IMAGES_STORE = './imgs_bobo'
		-指定开启的管道:自制的管道类
	注意:没有创建文件夹也没报错,可能是因为没有pillow这个库
	pip install pillow.
	
15.1爬虫文件
import scrapy
from ..items import ImgsproItem

class ImgSpider(scrapy.Spider):
    name = "img"
    # allowed_domains = ["www.xxx.com"]
    start_urls = ["https://sc.chinaz.com/tupian/"]

    def parse(self, response):
        div_list = response.xpath('/html/body/div[3]/div[2]/div')
        for div in div_list:
            # 注意:这里用到了伪属性,即当滑动页面时src2变成了src,src2就是伪属性,
            # 只有图片处在可视化的地方时,src2才会变成src,这里应该写src2,但是这里与up主情况不一样
            # 只需要取data-original属性即可
            src = div.xpath('./img/@data-original').extract_first()
            src = 'https:'+ src
            item = ImgsproItem()
            item['src'] = src

            yield item

15.2自制管道类
# 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


# useful for handling different item types with a single interface

from itemadapter import ItemAdapter


# class ImgsproPipeline:
#     def process_item(self, item, spider):
#         return item
import scrapy
from scrapy.pipelines.images import ImagesPipeline
class imgsPileLine(ImagesPipeline):
    # 根据图片地址进行图片数据请求
    def get_media_requests(self, item, info):

        yield scrapy.Request(item['src'])

    # 指定图片存储的路径,没有创建文件夹是因为没有这个库pillow,pip install pillow
    def file_path(self, request, response=None, info=None):
        imgName = request.url.split('/')[-1]
        return imgName

    def item_completed(self, results, item, info):
        return item # 返回给下一个即将被执行的管道类
1.五大核心组件
2.中间件
1.下载中间件
	-位置:引擎和下载器之间
	-作用:批量拦截到整个工程中所有的请求和响应
	-拦截请求:
		1.UA伪装:process_request
		2.代理IP:process_exception
		
	-拦截响应:
		1.篡改响应数据,响应对象

[3]/div[2]/div’)
for div in div_list:
# 注意:这里用到了伪属性,即当滑动页面时src2变成了src,src2就是伪属性,
# 只有图片处在可视化的地方时,src2才会变成src,这里应该写src2,但是这里与up主情况不一样
# 只需要取data-original属性即可
src = div.xpath(‘./img/@data-original’).extract_first()
src = ‘https:’+ src
item = ImgsproItem()
item[‘src’] = src

        yield item

##### 15.2自制管道类

```python
# 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


# useful for handling different item types with a single interface

from itemadapter import ItemAdapter


# class ImgsproPipeline:
#     def process_item(self, item, spider):
#         return item
import scrapy
from scrapy.pipelines.images import ImagesPipeline
class imgsPileLine(ImagesPipeline):
    # 根据图片地址进行图片数据请求
    def get_media_requests(self, item, info):

        yield scrapy.Request(item['src'])

    # 指定图片存储的路径,没有创建文件夹是因为没有这个库pillow,pip install pillow
    def file_path(self, request, response=None, info=None):
        imgName = request.url.split('/')[-1]
        return imgName

    def item_completed(self, results, item, info):
        return item # 返回给下一个即将被执行的管道类
1.五大核心组件

在这里插入图片描述

2.中间件
1.下载中间件
	-位置:引擎和下载器之间
	-作用:批量拦截到整个工程中所有的请求和响应
	-拦截请求:
		1.UA伪装:process_request
		2.代理IP:process_exception
		
	-拦截响应:
		1.篡改响应数据,响应对象

更多推荐