网络爬虫集合【看这一篇就够了】

文章目录

    • (一)爬虫初始
    • (二)数据请求
      • 2.0、urllib模块
          • 基本使用
          • 一个类型和6和方法
          • 下载
          • 请求对象的定制
          • 编/解码
          • ajax的get请求
          • ajax的post请求
          • URLError/HTTPError
          • Handler处理器
          • 代理服务器
      • 2.1、requests模块
        • 第一个爬虫程序
        • requests模块实战
    • (三)数据解析
        • 3.0概述
        • 3.1正则表达式
        • 3.2re模块
        • 3.3 bs4数据解析
        • 3.4xpath数据解析
        • 3.5jsonpaht数据解析
    • (四)爬虫进阶
        • 4.0验证码识别
        • 4.1 cookie
        • 4.2代理
        • 4.3防盗链
    • (五)异步爬虫
        • 5.0单线程
        • 5.1多线程
        • 5.2多进程
        • 案例:基于进程+线程实现多任务爬虫
        • 5.3线程池和进程池
        • 5.4协程
          • 5.4.1async & await关键字
          • 5.4.2事件循环
          • 5.4.3快速上手
          • 5.4.4await关键字
          • 5.4.5Task对象
          • 5.4.6async.Future对象
          • 5.4.7concurrent.futures.Future对象
          • 5.4.8异步和非异步
          • 5.4.9异步上下文管理器
        • 5.5 多任务异步协程
        • 5.6aiohttp模块
    • (六)动态加载数据处理
        • selenium
          • 6.0selenium模块使用
          • 6.1其他自动化操作
          • 6.2拉钩网自动化操作
          • 6.3窗口切换
          • 6.4动作链和iframe的处理
          • 6.5模拟登录qq空间
        • Phantomjs
        • Chrome handless
          • 固定配置
          • 代码封装
          • 谷歌无头浏览器+反检测
    • (七)scrapy框架
        • scrapy创建以及运行
        • scrapy架构组成
          • 五大核心组件
        • scrapy工作原理
        • scrapy shell
        • yield
        • 解析相关
        • 应用
          • 1.1scrapy持久化存储:
            • 基于本地存储
            • 基于管道存储
            • 开启多条管道
          • 1.2基于Spider的全站数据解析爬取
          • 1.3请求传参
        • 图片管道
          • 图片数据爬取之自定义ImagePipeline
        • CrawlSpider
        • CrawlSpider案例
        • 日志信息和等级
        • scrapy的post请求
    • 分布式爬虫

(一)爬虫初始

通过编写程序,模拟浏览器去上网,然后让其去互联网上抓取数据的过程

爬虫分类

  • 通用爬虫 :抓取系统重要组成部分,抓取一整张页面的数据
  • 聚焦爬虫:建立在通用爬虫基础之上,抓取的是页面中特定的局部内容
  • 增量式爬虫 :检测网站中数据更新的情况,只会抓取网站中最新更新出来的数据

爬虫的矛与盾

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

  • 反反爬策略: 爬虫程序可以通过指定相关策略或者技术手段,破解门户网站中具备的反爬机制,从而可以获取门户网站中相关的数据

robots.txt协议:

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

    #查看淘宝网站的robots.txt协议
    www.taobao.com/robots.txt
    

HTTP&HTTPS协议

  • 当前URL地址在数据传输的时候遵循的HTTP协议

  • 协议:就是两个计算机之间为了能够流畅的进行沟通而设置的一个君子协议,常见的协议有TCP/IP SOAP协议,HTTP协议, SMTP协议等…

  • HTTP协议: Hyper Text Transfer Protocol(超文本传输协议) 的缩写,是用于万维网服务器传输超文本到浏览器的传送协议

  • HTTPS协议:安全的超文本传输协议

  • 直白点,就是浏览器和服务器之间的数据交互遵守的HTTP协议

  • HTTP协议把一条消息分为三大快内容,无论是请求还是响应的三块内容

# 加密方式:
# 对称密钥加密,
# 非对称密钥加密,
# 证书密钥加密
# 请求:
'''
1.请求行--》请求方法(get/post) 请求url地址,协议
2.请求头--》放一些服务器要使用的附加信息
3
4.请求体--》一般放一些参数
'''
#请求头中常见的一些重要内容(爬虫需要)
'''
1.USer-Agent:请求载体的身份标识(用啥发送的请求)
2.Refer:防盗链(这次请求是从哪些页面来的? 反爬会用到)
3.cookie:本地字符串数据信息(用户登录信息,反爬的token)
Connection:请求完毕后,是保持连接还是断开连接
'''
#响应:
'''
1.状态行--》协议 状态码 (200 404 500)
2.响应头--》放一些客户端要使用的一些附加信息
3.
4.响应体--》服务器返回的真正客户端要使用的内容(HTML,json)等
'''
#响应头中一些重要内容
'''
Content-Type:服务器响应回客户端的数据类型
1.cookie:本地字符串数据信息(用户登录信息,反爬的token)
2.各种神奇的莫名其妙的字符串(这个需要经验了,一般都是token字样,防止各种攻击和反爬)
'''
#请求方式:
''''
GET:显示提交
POST:隐示提交
'''

反扒手段

网络爬虫集合【看这一篇就够了】_第1张图片

(二)数据请求

作用:模拟浏览器 发起请求

2.0、urllib模块

基本使用
# 使用urllib获取百度源码
import urllib.request

# 1. 定义一个url
url = 'http://www.baidu.com'
# 2. 模拟浏览器向服务器发送请求
response = urllib.request.urlopen(url)
# 3. 获取响应中的页面源码
content=response.read().decode('utf-8')   #read方法返回字节形式的二进制数据
# 4. 打印数据
print(content)
一个类型和6和方法
import urllib.request

url = 'http://www.baidu.com'
response = urllib.request.urlopen(url)

# 一个类型和6个方法
print(type(response))  #

# content=response.read()
# content = response.read(5)  # 返回5个字节
# content = response.readline()  # 读取一行
# content = response.readlines()  # 读取多行
# print(content)

print(response.getcode())#返回状态码
print(response.geturl())  #返回url地址
print(response.getheaders())  #获取状态信息
下载
import urllib.request

# 下载网页
# url_html = 'http://www.baidu.com'
# urllib.request.urlretrieve(url=url_html, filename='../data/百度.html')
# 下载图片
# url_img='https://gitee.com/zh_sng/cartographic-bed/raw/master/img/image-20230202185000596.png'
# urllib.request.urlretrieve(url_img,'../data/girl.png')
# 下载视频
# url_vedio='http://vd3.bdstatic.com/mda-pb158hh9ss4ev38n/cae_h264/1675309740554567465/mda-pb158hh9ss4ev38n.mp4'
# urllib.request.urlretrieve(url_vedio,'../data/vedio.mp4')
请求对象的定制
import urllib.request

url = 'https://www.baidu.com'

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
}

# 请求对象的定制 ,urlopen中不能存储字典,
request=urllib.request.Request(url=url,headers=headers)
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
print(content)
编/解码
  1. get请求的quote方法
import urllib.request
import urllib.parse
# https://www.baidu.com/s?ie=UTF-8&wd=%E5%91%A8%E6%9D%B0%E4%BC%A6

url = 'https://www.baidu.com/s?wd='
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
}
#将周杰伦三个字变成unicode编码格式
name=urllib.parse.quote('周杰伦')
url += url+name
request = urllib.request.Request(url=url, headers=headers)
response = urllib.request.urlopen(request)

content = response.read().decode('utf-8')
print(content)
  1. get请求的urlencode方法
import urllib.request
import urllib.parse

# urlencode应用场景:多个参数的时候
# https://www.baidu.com/s?wd=周杰伦&sex=男

base_url = 'https://www.baidu.com/s?'

data = {
    'wd': '周杰伦',
    'sex': '男'
}
data = urllib.parse.urlencode(data)
url = base_url + data

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
}
# 请求对象的定制
request = urllib.request.Request(url=url, headers=headers)
# 模拟浏览器箱服务器发送请求
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
print(content)

  1. post请求百度翻译
import urllib.request
import urllib.parse

# https://fanyi.baidu.com/langdetect
url = 'https://fanyi.baidu.com/langdetect'
# 请求头
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
}

data = {
    'query': 'Python'
}

# post请求参数必须要进行编码
data = urllib.parse.urlencode(data).encode('utf-8')
# post请求的参数不会拼接在url后面,而是放置在请求对象定制的参数中
request = urllib.request.Request(url=url, data=data, headers=headers)

# 模拟浏览器向服务器发送请求
response = urllib.request.urlopen(request)

#获取相应的数据
content=response.read().decode('utf-8')
print(content)
  1. post请求百度翻译之详细翻译
import urllib.request
import urllib.parse

url = 'https://fanyi.baidu.com/v2transapi?from=en&to=zh'

headers = {
    'Cookie': "BIDUPSID=563BF61659E928EF78958A45A41BEC59; PSTM=1673672643; BAIDUID=563BF61659E928EFB7C9D1115AAAF5FF:FG=1; BDUSS=Fd3RkpzZ3NSWlhTcmRUbDN4VEc5WXR4U2ZmZDN0VmUtcUcwY2ZIUXdIZFFhT3RqRVFBQUFBJCQAAAAAAAAAAAEAAACEELRO1dTLzNChza~QrAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFDbw2NQ28Nja; BDUSS_BFESS=Fd3RkpzZ3NSWlhTcmRUbDN4VEc5WXR4U2ZmZDN0VmUtcUcwY2ZIUXdIZFFhT3RqRVFBQUFBJCQAAAAAAAAAAAEAAACEELRO1dTLzNChza~QrAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFDbw2NQ28Nja; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; H_PS_PSSID=36558_38113_38093_37140_37906_37989_36807_37923_38087_26350_37959_38100_38008_37881; BAIDUID_BFESS=563BF61659E928EFB7C9D1115AAAF5FF': 'FG=1; Hm_lvt_64ecd82404c51e03dc91cb9e8c025574=1675339841; Hm_lpvt_64ecd82404c51e03dc91cb9e8c025574=1675339848; APPGUIDE_10_0_2=1; REALTIME_TRANS_SWITCH=1; FANYI_WORD_SWITCH=1; HISTORY_SWITCH=1; SOUND_SPD_SWITCH=1; SOUND_PREFER_SWITCH=1; ab_sr=1.0.1_ODg2ZDZjMTY1ZmYzODkxYmQzZTY2OTdjY2VlMmZlNWIzZTMxMDExZjZkZTk0Mjc4M2U2NjQ4YTYyZGIwMjNmYzJmNzRkZDYzMDhkOWE2ZmZhZTUxYTMxZGNmZWFhNzA0ODdkNTljZDlkNWRjZDQ3MjE3NTY4ZTEzMjgzMGI3ZjY5ZTA2YzFmZWQ0ZTc2ZTNhMjYyOWE1NjIzMjlkZjVkMTViZDY2ODVhNzQzNzFiZDQ0YWFlNDg4NDc1MTNiYjNl",
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36',
}
data = {
    'from': 'en',
    'to': 'zh',
    'query': 'Python',
    'transtype': 'enter',
    'simple_means_flag': 3,
    'sign': 587387.791882,
    'token': '24a5c7161e43cff295e8c08eae4fa83f',
    'domain': 'common',
}
data = urllib.parse.urlencode(data).encode('utf-8')
request = urllib.request.Request(url=url, data=data, headers=headers)
response = urllib.request.urlopen(request)
content = response.read().decode('utf-8')
print(content)
ajax的get请求

豆瓣电影持久化存储第一页数据

import urllib.request
import urllib.parse

url = 'https://movie.douban.com/j/chart/top_list?type=5&interval_id=100%3A90&action=&start=0&limit=20'

# 获取豆瓣电影第一页的数据,并且保存
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
}

# 请求对象定制
request = urllib.request.Request(url=url, headers=headers)
# 获取响应数据
response = urllib.request.urlopen(request)

#获取数据进行持久化存储
content=response.read().decode('utf-8')

with open('../data/douban.json',mode='w',encoding='utf-8') as f:
    f.write(content)

ajax的get请求之豆瓣电影持久化存储前十页数据

import urllib.request
import urllib.parse
import os

# 获取豆瓣电影前十页的数据,并且保存


def create_request(page):
    base_url = f'https://movie.douban.com/j/chart/top_list?type=5&interval_id=100%3A90&action='
    data = {
        'start': (page - 1) * 20,
        'limit': 20
    }
    data = urllib.parse.urlencode(data)
    url = base_url + data

    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
    }
    # 请求对象定制
    request = urllib.request.Request(url=url, headers=headers)
    return request


def get_content(request):
    resposne = urllib.request.urlopen(request,timeout=2)
    content = resposne.read().decode('utf-8')
    return content


def download(page, content):
    with open('../data/douban/douban' + str(page) + '.json', 'w', encoding='utf-8') as f:
        print(f'正在下载第{str(page)}页,请耐心等待.....')
        f.write(content)

if __name__ == '__main__':
    # 创建数据的保存目录
    if not  os.path.exists('../data/douban'):
        os.mkdir('../data/douban')

    start_page = int(input('请输入起始页码:'))
    end_page = int(input('请输入结束的页码:'))
    for page in range(start_page, end_page + 1):
        # 每一页都有自己的请求对象的定制
        request = create_request(page)
        # 获取响应数据
        content = get_content(request)
        # 下载
        download(page, content)
    print(f'\n第{start_page}页到{end_page}下载完毕,请注意查看....')

ajax的post请求
import os
import urllib.request
import urllib.parse
import urllib.error

def create_request(page):
    url = "http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=cname"
    data = {
        'cname': '北京',
        'pid': '',
        'pageIndex': page,
        'pageSize': 10
    }
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
    }
    data = urllib.parse.urlencode(data).encode('utf-8')
    request = urllib.request.Request(url=url, data=data, headers=headers)
    return request


def get_content(request):
    try:
        resposne = urllib.request.urlopen(request,timeout=2)
        content = resposne.read().decode('utf-8')
        return content
    except urllib.error.URLError as e:
        print(e)


def download(content, page):
    with open('../data/kfc/kfc_' + str(page) + '.json', mode='w', encoding='utf-8') as f:
        print('正在保存第%s页数据...'.format(str(page)))
        f.write(content)


if __name__ == '__main__':
    if not os.path.exists('../data/kfc'):
        os.mkdir('../data/kfc')
    start_page = int(input('请输入起始页码:'))
    end_page = int(input('请输入结束页码:'))
    for page in (start_page, end_page + 1):
        # 请求对象定制
        request = create_request(page)
        # 获取网页数据
        content = get_content(request)
        # 下载
        download(content, page)
    print('\n\n保存结束')

URLError/HTTPError
1.HTTPErroe类是URLError类的子类
2.导入包urllib.error.HTTPError     urllib.error.URLError
3.http错误:http错误是针对浏览器无法连接到服务器而增加出来的错误提示,引导并告诉浏览器者该页是哪里出了问题
4.通过urllib发送请求的时候,有可能会发送失败,可以通过try-execpt进行捕获异常,异常类有两个:URLError\HTTPError
Handler处理器
import urllib.request
import urllib.parse

# 使用Handler来访问百度,获取网页源码
url='http://www.baidu.com'
headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
    }
request=urllib.request.Request(url=url,headers=headers)

# handler build_opener open

# 1. 获取handler对象
handler=urllib.request.HTTPSHandler()
# 2. 获取opener对象
opener=urllib.request.build_opener(handler)
# 3. 调用open方法
response=opener.open(request)
content=response.read().decode('utf-8')
print(content)
代理服务器

快代理

1.代理的常用功能?
    1.突破自身1P访问限,访问国外站点.
    2.访问一些单位或团体内部资通
    	扩展:某大学年TP(前提是该代理地址在该资源的允许访问范园之内),使用教育网内地址设免费代理服务器,就可以用于对教育网开放的各类FTP下载上传,以及各类资料直间共享等服务。
    3.提高访问速度
    	扩展:通常代理服务器都设置一个较大的硬白缓冲区,当有外界的信息通过时,同时也将其保存到暖冲区中,当其他用户再访问相同的信息时,则直接由领冲区中取出信息,传给用户,以提高访问速度。
    4.隐真实1P
    	扩展:上网者也可以通过这种方法隐藏自己的P,免受攻击。
2.代码配置代理
    创建Reugest对象
    创建ProxyHandleri对象
    用handler对象创建opener对家
    使用opener,open函数发送请求
import urllib.request

url='https://www.baidu.com/s?ie=UTF-8&wd=ip'
headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
    }
request=urllib.request.Request(url=url,headers=headers)
proxies={
   'http': '118.24.219.151:16817'
}
#模拟浏览器访问服务器
handler=urllib.request.ProxyHandler(proxies=proxies)
opener=urllib.request.build_opener(handler)
resposne=opener.open(request)

#获取相应信息
content=resposne.read().decode('utf-8')
with open('../data/代理.html','w',encoding='utf-8') as f:
    f.write(content)

代理池

import  urllib.request
proxies_pool=[
    {'http':'118.24.219.151:16817'},
    {'http':'112.14.47.6:52024'},
    {'http':'222.74.73.202:42055'},
    {'http':'114.233.70.231:9000'},
    {'http':'116.9.163.205:58080'},
    {'http':'27.42.168.46:55481'},
    {'http':'121.13.252.61:41564'},
    {'http':'61.216.156.222:60808'},
]
import  random

proxies=random.choice(proxies_pool)

url='https://www.baidu.com/s?ie=UTF-8&wd=ip'
headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
}
request=urllib.request.Request(url=url,headers=headers)

handler=urllib.request.ProxyHandler(proxies=proxies)
opener=urllib.request.build_opener(handler)
response=opener.open(request)

content=response.read().decode('utf-8')
with open('../data/代理.html','w',encoding='utf-8') as f:
    f.write(content)

2.1、requests模块

pip install requests
response的属性以及类型
    类型      		:models.Response
    r.text				:获取网站源码
    r.encoding			:访问或定制编码方式
    r.url				:获取请求的urI
    r.content			:响应的字节类型
    r.status_code		:响应的状态码
    r.headers			:响应的头信息

第一个爬虫程序

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

# 导包
import requsets
# 指定url
url='https://www.sogou.com/'
# 发起请求
#get方法会返回一个响应对象
response=requests.get(url=url)
# 获取响应数据:调用响应对象的text属性,  返回响应对象中存储的是字符串形式的响应数据(页面源码数据)
page_text=response.text
print(page_text)
# 持久化存储
with open("sougou.html",'w',encoding='utf-8') as f:   		f.write(page_text)
print('爬取结束')

requests模块实战

  • text 返回字符串
  • content 返回二进制
  • json() 返回对象

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

import requests
get_url='https://www.sogou.com/web?'
#处理url携带的参数,存到字典中
kw=input("enter a message:")
param={
    "query":kw
}
#UA伪装 :让爬虫对应的请求载体身份标识 伪装成某一款浏览器
#UA:User-Agent  门户网站的服务器会检测对应请求的载体身份标识,如果检测到请求载体的身份标识是一款浏览器,,说明是一个正常的请求,但是检测到请求载体不是某一款浏览器,则表示该请求不正常(crawl),服务器端拒绝请求
headers={
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"
}
response=requests.get(url=get_url,params=param,headers=headers)
page_text=response.text
# print(page_text)
FielName=kw+'.html'
with open(FielName,'w',encoding='utf-8') as f:
    f.write(page_text)
print(kw +' save  over!!')

案例二:百度翻译

#拿到当前单词所对应的翻译结果
#页面局部刷新 ajax
import json
import requests
post_url='https://fanyi.baidu.com/sug'

headers={
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"
}
word=input('请输入要翻译的单词:enter a word!!\n')
#post请求参数处理
data={
    "kw":word
}
response=requests.post(url=post_url,data=data,headers=headers)
# print(response.text)
dic_obj=response.json()#json返回的是字典对象,确认响应数据是json类型,才可使用json()
with open(word+'.json','w',encoding='utf-8') as f:
    json.dump(dic_obj,fp=f,ensure_ascii=False)
print('over')

案例三:豆瓣电影

#页面局部刷新,发起ajax请求
import json
import requests
get_url='https://movie.douban.com/j/chart/top_list'
headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"
}
param={
    'type':'24',
    'interval_id' : '100:90',
    'action': '',
    'start': '0',#从库中第几部电影去爬取
    'limit': '20', #一次取出多少个
}

respons=requests.get(url=get_url,params=param,headers=headers)
list_data=respons.json()
fp=open('./douban.json','w',encoding='utf-8')
json.dump(list_data,fp,ensure_ascii=False)
fp.close()
print('over')

案例四:肯德基官网餐厅查询,爬取多页餐厅信息

动态加载,局部刷新,
import json
import requests

post_url = 'http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword'
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"
}

for i in range(1,11):
    data = {
        'cname': '',
        'pid': '',
        'keyword': '北京',
        'pageIndex': i,
        'pageSize': '10',
    }

    response requests.post(url=post_url,headers=headers, data=data)
    dic_data = response.text
    with open('KFC_order', 'a', encoding='utf-8') as f:
        f.write(dic_data)
        print('\n')
#爬取肯德基餐厅第一页数据
import requests,os

post_url='http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword'

headers={
    "User-Agent":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36"
}
mes= input('请输入一个城市:')
data={
    'cname':'' ,
    'pid':'',
    'keyword': mes,
    'pageIndex': '1',
    'pageSize': '10',
}
#发送请求
response=requests.post(url=post_url,headers=headers,data=data)
#获取响应
result=response.text
if not os.path.exists('./网络爬虫/sucai/KFC/'):
    os.makedirs('./网络爬虫/sucai/KFC/')
#持久化存储
with open('./网络爬虫/sucai/KFC/'+mes+'KFC','w',encoding='utf-8') as f:
    f.write(result)

(三)数据解析

3.0概述

回顾requests模块实现爬虫的步骤:

  1. 指定url
  2. 发情请求
  3. 获取响应数据
  4. 持久化存储

起始在持久化存储之前还有一步数据解析,需要使用聚焦爬虫,爬取网页中部分数据,而不是整个页面的数据。下面会学习到三种数据解析的方式。至此,爬虫流程可以修改为:

  1. 指定url
  2. 发起请求
  3. 获取响应
  4. 数据解析
  5. 持久化存储

数据解析分类:

  • 正则
  • bs4
  • xpath

数据解析原理概述:

  • 解析的局部的文本内容都会在标签之间或者标签对应的属性中进行存储
  • 进行指定标签的定位
  • 标签或者标签对应的属性中存储的数据值进行提取(解析

3.1正则表达式

# 元字符:具有固定含义的特殊符号
#常用元字符
'''
.  匹配除换行符以外的任意字符
\w 匹配字母或数字或下划线
\s 匹配任意的空白符
\d 匹配数字
\n 匹配一个换行符
\t 匹配一个制表符

^  匹配字符串的开始
$  匹配字符串结尾

\W        匹配非字母或数字或下划线
\S        匹配非空白符
\D        匹配非数字
a|b       匹配字符a 或者字符b
()        匹配括号内的表达式,也表示一个组
[.....]   匹配字符组中的字符
[^......] 匹配除了字符串中的字符的所有字符
'''
#量词:控制前面的元字符出现的次数
'''
*        重复零次或更多次
+        重复一次或者更多次
?       重复零次或一次
{n}      重复n次
{n,}     重复n次或者更多次
{n,m}    重复n到m次
'''
#贪婪匹配和惰性匹配
'''
.*  贪婪匹配
.*? 惰性匹配
'''

3.2re模块

import re

 #findall:匹配字符串中所有的符合正则的内容【返回的是列表】
list =re.findall(r'\d+',"我的电话号码是:10086,我女朋友的电话是:10010")
print(list)

# #finditer: 匹配字符串中所有的内容【返回的是迭代器】,从迭代器中拿到内容需要.group()
it =re.finditer(r'\d+',"我的电话号码是:10086,我女朋友的电话是:10010")
for item in it:
     print(item)
     print(item.group())

# search 找到一个结果就返回,返回的结果是match对象,拿数据需要.group(),  【全文匹配】
s= re.search(r'\d+',"我的电话号码是:10086,我女朋友的电话是:10010")
print(s.group())

#match是从头匹配
s=re.match(r'\d+',"10086,我女朋友的电话是:10010")
print(s.group())

#预加载正则表达式
s='''
郭麒麟
白鹿
李一桐
'''
#(?P<分组名字>正则) 可以单独从正则匹配的内容中进一步提取到内容 obj=re.compile("
(?P.*?)
"
) result=obj.finditer(s) for item in result: print(item.group("daihao")) print(item.group("number")) print(item.group("name"))

案例一:豆瓣排行

import csv

import requests
import re

url ="https://movie.douban.com/top250"
header={
    "User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"
}
# 获取响应
res=requests.get(url=url,headers=header)

content_page=res.text
# print(content_page)
#解析数据
obj=re.compile(r'
  • .*?(?P.*?).*?' r'

    .*?
    (?P

    .*?' r'(?P.*?).*?' r'(?P.*?)' ,re.S) #开始匹配 result=obj.finditer(content_page) f=open("data.csv",mode="w",encoding='utf-8') cswriter=csv.writer(f) for item in result: # print(item.group("name")) # print(item.group("time").strip()) # print(item.group("grade")) # print(item.group("number")) dic=item.groupdict() dic['time']=dic['time'].strip() cswriter.writerow(dic.values()) f.close() print("over")
  • 3.3 bs4数据解析

    数据解析原理

       1. 	标签定位,
       2. 提取标签,标签属性中存储的数据值
          实例化一个beautifulSoup对象,并且将页面源码数据加载到该对象 中
       3. 通过调用BeautifulSoup对象中相关属性或者方法进行标签定位和数据提取
    

    环境安装:

    pip install bs4
    pip install lxml
    

    如何实例化BeautifulSoup对象

    • from bs4 import BeautifulSoup
    • 对象实例化:

      1. 将本地的html文档中的数据加载到该对象中
      fp=open('../01_初识爬虫/周杰伦.html','r',encoding='utf-8')
      soup=BeautifulSoup(fp,'lxml')
      print(soup)
      
      1. 将互联网上获取的页面源码加载到该对象中
      page_text=response.text
      soup=BeautifulSoup(page_text,'lxml')
      print(soup)
      
    * 提供的用于数据解析的方法和属性:
    1. soup.tageName:返回的是文档中第一次出现的tageName对应的标签内容
    
    2. soup.find():
        * find('tageName'):等同于soup.div
        * 属性定位:
            - soup.find('div',class_='song')
            
    3. soup.find_all('a')#返回符合要求的所有的标签,返回列表
       soup.find_all(['a','span']) #获取多个标签,需要放在列表中.
       soup.find_all('li',limit=2)  #查找前两个数据
    
    4. soup.select('.tang') #class_='tang'  返回复数,列表里
        * soup.select('某种选择器') 选择器可以是id,class ,标签选择器
        	soup.select('.c1') 
          	soup.select('#l1')
            soup.select('li[id]') # 查找li标签有id的标签
            soup.select('li[id="l2"]') #查找li标签中id为l2的标签
            
        * 层级选择器使用:
            * soup.select('.tang > ul > li >a')[0]    
               li > a # 子代选择器 用于选择某个元素下所有的一级子元素
            * soup.select('.tang > ul > li a')[0]
               li a  # 后代选择器  用来选择某个元素的所有的后代元素
            * soup.select('li ~ a') #兄弟选择器  用于选取某个(或某些)元素的同级目录下所有的后面的标记
            * soup.select('div + a')  # 相邻兄弟选择器  用于选取某个(或某些)元素同级目录下的下一个元素
            * soup.select('a,li')   #标签a和标签li所有的数据
            
    5. 获取标签中的文本数据
        - soup.a.text /string/get_text()
        > text 和 get_text():可以获取一个某标签中所有的文本内容
            string只可以获取该标签下面直系的文本内容
            
    6. 获取标签中的属性值
        - soup.a['href']
        - soup.a.get('href')
        
    7. 获取标签的属性和属性值
        - soup.a.attrs 
        
    8. 获取标签的名字
    	- ob=soup.select('#p1')[0].name
    	- print(ob)
    
    

    案例一:爬取三国演义小说所有章节标题和章节内容

    三国演义

    import requests
    from bs4 import BeautifulSoup
    from fake_useragent import  UserAgent
     
    if __name__ =='__main__':
        headers={
            "User-Agent":UserAgent().chrome
        }
     	get_url='https://www.shicimingju.com/book/sanguoyanyi.html'
        #发起请求,获取响应
        page_text=requests.get(url=get_url,headers=headers).text.encode('ISO-8859-1')
        #在首页中解析出章节标题和章节内容
        #1. 实例化BeautifulSoup对象,将html数据加载到该对象中
        soup=BeautifulSoup(page_text,'lxml')
        # print(soup)
        #2.解析章节标题和详情页的url
        list_data=soup.select('.book-mulu > ul > li')
        fp=open('./sanguo.text','w',encoding='utf-8')
        for i in list_data:
            title=i.a.text
            detail_url='https://www.shicimingju.com/'+ i.a['href']
            #对详情页的url发送请求,
            detail_text=requests.get(url=detail_url,headers=headers).text.encode('ISO-8859-1')
            detail_soup=BeautifulSoup(detail_text,'lxml')
            #获取章节内容
            content=detail_soup.find('div',class_='chapter_content').text
    
            #持久化存储
            fp.write(title+":"+content+"\n")
            print(title,'下载完成')
    

    **案例二:**星巴克菜单名

    from bs4 import BeautifulSoup
    
    import urllib.request
    
    url = 'https://www.starbucks.com.cn/menu/'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
    }
    
    request = urllib.request.Request(url=url, headers=headers)
    response = urllib.request.urlopen(request)
    content = response.read().decode('utf-8')
    
    soup = BeautifulSoup(content, 'lxml')
    # //ul[@class="grid padded-3 product"]//strong/text()
    
    name_list=soup.select('ul[class="grid padded-3 product"] strong')
    for name in name_list:
        print(name.get_text())
    
    import requests
    from bs4 import BeautifulSoup
    import time
    
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
    }
    
    def get(url):
        response = requests.get(url, headers=headers)
        if response.status_code == 200:
            response.encoding = 'utf-8'
            content = response.text
            parse(content)
    
    def parse(html):
        soup = BeautifulSoup(html, 'lxml')
        # //ul[@class="grid padded-3 product"]/li/a/strong/text()
        name_list = soup.select('ul[class="grid padded-3 product"] strong')
        lists = []
        for name in name_list:
            lists.append(name.get_text())
        itemipiline(lists)
    
    import csv
    def itemipiline(name):
        for i in name:
            with open('星巴克.csv', 'a', encoding='utf-8') as fp:
                writer = csv.writer(fp)
                writer.writerows([i])
    
    if __name__ == '__main__':
        url = 'https://www.starbucks.com.cn/menu/'
        get(url)
    

    3.4xpath数据解析

    1. 安装lxml库
    	pip install lxml
    2. 导入 lxml.etree
    	from lxml import etree
    3. etree.parse() 解析本地文件
    	html_tree=etree.parse('xx.html')
    4. etree.HTML()  解析服务器响应文件
    	html_tree=etree.HTML(response.read().decode("utf-8"))
    5. html_tree.xpath(xpath路径)
    
    • 实例化etree对象,且将被解析的源码加载到该对象中
    • 调用etree对象中的xpath方法,结合着xpath表达式实现标签定位和内容的捕获
    xpath基本语法:
        1.路径查询
            //:查找所有子孙节点,不考虑层级关系
            /:找直接子节点
        2.谓词查询
            //div[@id]
            //div[@id="maincontent"]
        3.属性查询
            //@class
        4.模糊查询
            //div[contains(@id,"he")]
            //div[starts-with(@id,"he")]
        5.内容查询
            //div/h1/text()
        6.逻辑运算
            //div[@id="head"and @class="s_down"]
            //title | //price
    
    <body>
    <ul>
        <li id="l1" class="c1">北京li>
        <li id="l2">上海li>
        <li id="c3">山东li>
        <li id="c4">四川li>
    ul>
    body>
    
    from lxml import etree
    
    # xpath解析本地文件
    tree = etree.parse('../../data/xpath的基本使用.html')
    
    # 查找url下的li
    # li_list=tree.xpath('//body/ul/li')
    
    # 查找所有id的属性的li标签
    # text()获取标签中的内容
    # li_list = tree.xpath('//ul/li[@id]/text()')
    
    # 找到id为l1的li标签,注意引号问题
    # li_list=tree.xpath('//ul/li[@id="l1"]/text()')
    
    # 查找到id为l1的li标签的class属性值
    # li_list=tree.xpath('//ul/li[@id="l1"]/@class')
    
    # 查找id中包含l的li标签
    # li_list=tree.xpath('//ul/li[contains(@id ,"l")]/text()')
    
    # 查找id的值以c开头的li标签
    # li_list = tree.xpath('//ul/li[starts-with(@id ,"c")]/text()')
    
    #查询id为li和class为c1的
    # li_list = tree.xpath('//ul/li[@id="l1" and @class="c1"]/text()')
    
    #查询
    li_list = tree.xpath('//ul/li[@id="l1"]/text() | //ul/li[@id="l2"]/text()')
    # 判断列表的长度
    print(len(li_list))
    print(li_list)
    

    **案例零:**获取百度网站的百度一下

    from lxml import etree
    import urllib.request
    
    # 1.获取网页源码
    # 2.解析服务器响应的文件 etree.HTML
    # 3.打印
    
    url = 'https://www.baidu.com/'
    
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
    }
    
    # 请求对象定制
    request = urllib.request.Request(url=url, headers=headers)
    # 模拟浏览器向服务器获取响应
    response = urllib.request.urlopen(request)
    # 获取网页源码
    content = response.read().decode('utf-8')
    
    # 解析网页源码,获取数据
    tree = etree.HTML(content)
    #xpath返回值是一个列表
    res = tree.xpath('//input[@id="su"]/@value')[0]
    print(res)
    

    案例一:彼岸图网

    import requests
    from lxml import etree
    import os
    
    from fake_useragent import UserAgent
    headers={
        "User-Agent":UserAgent().chrome
    }
    url='https://pic.netbian.com/4kdongman/'
    #发请求,获取响应
    response=requests.get(url=url,headers=headers)
    page_text=response.text
    
    #数据解析 src属性  alt属性
    tree=etree.HTML(page_text)
    list_data=tree.xpath('//div[@class="slist"]/ul/li')
    #print(list_data)
    
    #创建文件夹
    if not os.path.exists('./PicLibs'):
        os.mkdir('./PicLibs')
    for li in list_data:
        img_src='https://pic.netbian.com' + li.xpath('./a/img/@src')[0]
        img_name=li.xpath('./a/img/@alt')[0]+'.jpg'
        #print(img_name,img_src)
        #通用解决中文乱码的解决方案
        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,'下载成功!!!!')
    

    案例二:发表情

    import requests,os
    from lxml import etree
    from fake_useragent import UserAgent
    
    def crawl(url):
        headers={
            "User-Agent":UserAgent().chrome
        }
        #获取页面响应信息
        page_text=requests.get(url,headers).text
        #解析表情包的详情页url
        tree=etree.HTML(page_text)
        list_data=tree.xpath('//div[@class="ui segment imghover"]/div/a')
        if not os.path.exists('表情包'):
            os.mkdir('表情包')
        for i in list_data:
            detail_url='https://www.fabiaoqing.com'+i.xpath('./@href')[0]
            # print(detail_url)
            #对详情页面url发起请求,获取响应
            detail_page_text=requests.get(detail_url,headers).text
            tree=etree.HTML(detail_page_text)
            #得到搞笑图片的地址,发起请求进行持久化存储
            detail_list_data=tree.xpath('//div[@class="swiper-wrapper"]/div/img/@src')[0]
            fp=detail_list_data.split('/')[-1]
            with open('表情包/'+fp, 'wb') as fp:
                fp.write(requests.get(detail_list_data).content)
                print(fp,'下载完了!!!')
    #调用
    crawl('https://www.fabiaoqing.com/biaoqing/lists/page/1.html')
    

    案例三:绝对领域

    import  requests
    from fake_useragent import  UserAgent
    from lxml import  etree
    import os
    def crawl():
        url='https://www.jdlingyu.com/tuji'
        headers={
            "User-Agent":UserAgent().chrome
        }
        #获取页面代码
        page_text=requests.get(url=url,headers=headers).text
        #print(page_text)
        tree=etree.HTML(page_text)
        list_url=tree.xpath('//li[@class="post-list-item item-post-style-1"]/div/div/a')
        #print(list_url)
    
    
        for i in list_url:
            detail_link=i.xpath('./@href')[0]
            #print(detail_link)
            #对详情页url发请求,解析详情页图片
            detail_data=requests.get(detail_link,headers).text
            tree=etree.HTML(detail_data)
            #名称
            list_name=tree.xpath('//article[@class="single-article b2-radius box"]//h1/text()')[0]
            # 创建一个相册文件夹文件夹
    
            if not os.path.exists('绝对领域\\'+list_name):
                os.makedirs('绝对领域\\'+list_name)
            #图片链接
            list_link=tree.xpath('//div[@class="entry-content"]/p')
    
            for i in list_link:
                img_src=i.xpath('./img/@src')[0]
                # 图片名称
                pic_name=img_src.split('/')[-1]
                with open(f'绝对领域\\{list_name}\\{pic_name}','wb') as fp:
                    fp.write(requests.get(img_src).content)
                    print(list_name,'下载完成')
    
    
    crawl()
    

    案例四:爬取站长素材中免费简历模板

    # 爬取站长素材中免费简历模板    官网:https://sc.chinaz.com/
    from lxml import etree
    import requests,os
    import time 
    if __name__ == '__main__':
        url = 'https://sc.chinaz.com/jianli/free.html'
        headers = {
            "user-agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36"
        }
        # 获取免费简历模板网页数据
        page_text = requests.get(url=url, headers=headers).text
        #print(page_text)
        #解析数据,获取页面模板对应详情页面的url
        tree=etree.HTML(page_text)
        list_data= tree.xpath('//div[@id="main"]/div/div')
        #print(list_data)
        if not os.path.exists('./网络爬虫/sucai/站长素材'):
            os.makedirs('./网络爬虫/sucai/站长素材')
        for i in list_data:
            #解析出先要的详情页面链接
            detail_url ='https:'+i.xpath('./a/@href')[0]
            #获取简历模板名字
            detail_name =i.xpath('./p/a/text()')[0]
            detail_name=detail_name.encode('iso-8859-1').decode('utf-8')#解决中文编码问题
            #print(detail_name)
            #print(detail_url)
            #对详情页url发送请求,进行下载
            detail_data_text=requests.get(url=detail_url,headers=headers).text
            #进行数据解析,得到下载地址
            tree=etree.HTML(detail_data_text)
            down_link= tree.xpath('//div[@class="clearfix mt20 downlist"]/ul/li[1]/a/@href')[0]
            #print(down_link)
            #最后对下载地址链接发送请求,获取二进制数据
            final_data= requests.get(url=down_link,headers=headers).content
            #print(final_data)
            file_path='./网络爬虫/sucai/站长素材/'+detail_name
            with open(file_path,'wb') as fp:
                fp.write(final_data)
                time.sleep(1)  #下载延时1秒,防止爬取太快
                print(detail_name+'下载成功!!!')
    

    **案例五:**站长素材高清风景图片

    import urllib.request
    from lxml import etree
    import os
    
    def create_request(page):
        if page == 1:
            url = 'https://sc.chinaz.com/tupian/fengjingtupian.html'
        else:
            url = f'https://sc.chinaz.com/tupian/fengjingtupian_{str(page)}.html'
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
        }
        request = urllib.request.Request(url=url, headers=headers)
        return request
    
    def get_content(request):
        response = urllib.request.urlopen(request)
        content = response.read().decode('utf-8')
        return content
    
    def download(content):
        # 下载图片
        #'D:\图片\风景'
        tree=etree.HTML(content)
        src_list=tree.xpath('//body/div[3]/div[2]/div/img/@data-original')
        name_list=tree.xpath('//body/div[3]/div[2]/div/img/@alt')
        # urllib.request.urlretrieve('','D:\图片\风景')
        for i in range(len(name_list)):
            name=name_list[i]
            url='https:'+src_list[i]
            print('正在下载 %s [%s]' % (name, url))
            urllib.request.urlretrieve(url=url,filename=fr'D:\图片\风景\{name}.jpg')
    
    
    if __name__ == '__main__':
        if not os.path.exists('D:\图片\风景'):
            os.mkdir('D:\图片\风景')
        start_page = int(input('请输入起始页码:'))
        end_page = int(input('请输入结束页码:'))
        for page in range(start_page, end_page + 1):
            # 1.请求对象的定制
            request = create_request(page)
            # 2.获取网页源码
            content = get_content(request)
            # 3.下载
            download(content)
    

    3.5jsonpaht数据解析

    安装
    	pip install jsonpath
    使用
    	obj=json.load(open('json文件','r',encoding="utf-8"))
        ret=jsonpath.jsonpath(obj,'jsonpah语法')
    

    博客教程

    网络爬虫集合【看这一篇就够了】_第2张图片

    json格式数据

    { "store": {
        "book": [
          { "category": "reference",
            "author": "Nigel Rees",
            "title": "Sayings of the Century",
            "price": 8.95
          },
          { "category": "fiction",
            "author": "Evelyn Waugh",
            "title": "Sword of Honour",
            "price": 12.99
          },
          { "category": "fiction",
            "author": "Herman Melville",
            "title": "Moby Dick",
            "isbn": "0-553-21311-3",
            "price": 8.99
          },
          { "category": "fiction",
            "author": "J. R. R. Tolkien",
            "title": "The Lord of the Rings",
            "isbn": "0-395-19395-8",
            "price": 22.99
          }
        ],
        "bicycle": {
          "author":"老王",
          "color": "red",
          "price": 19.95
        }
      }
    }
    

    提取数据

    import jsonpath
    import json
    
    obj = json.load(open('../../../data/jsonpath.json', 'r', encoding='utf-8'))
    
    # 书店所有的书的作者
    # author_list=jsonpath.jsonpath(obj,'$.store.book[*].author')
    
    
    # 所有的作者
    # author_list =jsonpath.jsonpath(obj,'$..author')
    
    # store的所有元素。所有的bookst和bicycle
    # tag_list=jsonpath.jsonpath(obj,'$.store.*')
    
    # store里面所有东西的price
    # tag_list = jsonpath.jsonpath(obj, '$.store..price')
    
    
    # 第三个书
    # book=jsonpath.jsonpath(obj,'$..book[2]')
    
    # 最后一本书
    # book=jsonpath.jsonpath(obj,'$..book[(@.length-1)]')
    
    # 前面的两本书。
    # book=jsonpath.jsonpath(obj,'$..book[0,1]')
    # book=jsonpath.jsonpath(obj,'$..book[:2]')
    
    # 过滤出所有的包含isbn的书。
    # book = jsonpath.jsonpath(obj, '$..book[?(@.isbn)]')
    
    #过滤出价格高于10的书。
    book=jsonpath.jsonpath(obj,'$..book[?(@.price>10)]')
    print(book)
    

    (四)爬虫进阶

    4.0验证码识别

    • 验证码是门户网站中采取的一种反扒机制

    • 反扒机制:验证码,识别验证码图片中的数据,用于模拟登陆操作

    4.1 cookie

    • http/https协议特性:无状态

    • 没有请求到对应页面数据的原因:发起第二次基于个人主页请求的时候,服务器端并不知道该此请求是基于登陆状态下的请求

    • cookie:

    用来让服务器端记录客户端的相关信息

    1. 手动处理:通过抓包工具获取cookie值,将该值封装到headers中 (不建议)
    2. 自动处理: 进行模拟登陆post请求后,由服务器创建,
    • session会话对象
      • 作用:1. 可以进行请求发送
        2.如果请求过程中产生了cookie,则该cookie会被自动存储/携带在该session对象中
    • 使用:
      1. 创建session对象
      2. 使用session对象进行模拟登陆post请求的发送,(cookie就会被存储在session中)
      3. session对象对个人主页对应的get请求进行发送(携带了cookie)
    #17k小说网
    import requests
    
    
    #会话
    session=requests.session()
    data={
        'loginName': '自己的账户名',
        'passwod': '自己的密码',
    }
    #1.登陆
    url='https://passport.17k.com/ck/user/login'
    
    rs=session.post(url=url,data=data)
    #print(response.text)
    #print(response.cookies)#看cookie
    #2.拿数据
    #使用携带cookie的session进行get请求发送
    response=session.get('https://user.17k.com/ck/author/shelf?page=1&appKey=2406394919')
    print(response.json())
    

    另一种携带cookie访问

    直接把cookie放在headers中(不建议)

    #另一种携带cookie访问
    res=requests.get('https://user.17k.com/ck/author/shelf?page=1&appKey=2406394919',headers={
        "Cookie":"GUID=868e19f9-5bb3-4a1e-b416-94e1d2713f04; BAIDU_SSP_lcr=https://www.baidu.com/link?url=r1vJtpZZQR2eRMiyq3NsP6WYUA45n6RSDk9IQMZ-lDT2fAmv28pizBTds9tE2dGm&wd=&eqid=f689d9020000de600000000462ce7cd7; Hm_lvt_9793f42b498361373512340937deb2a0=1657699549; sajssdk_2015_cross_new_user=1; c_channel=0; c_csc=web; accessToken=avatarUrl%3Dhttps%253A%252F%252Fcdn.static.17k.com%252Fuser%252Favatar%252F08%252F08%252F86%252F97328608.jpg-88x88%253Fv%253D1657699654000%26id%3D97328608%26nickname%3D%25E9%2585%25B8%25E8%25BE%25A3%25E9%25B8%25A1%25E5%259D%2597%26e%3D1673251686%26s%3Dba90dcad84b8da40; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%2297328608%22%2C%22%24device_id%22%3A%22181f697be441a0-087c370fff0f44-521e311e-1764000-181f697be458c0%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_referrer%22%3A%22%22%2C%22%24latest_referrer_host%22%3A%22%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%7D%2C%22first_id%22%3A%22868e19f9-5bb3-4a1e-b416-94e1d2713f04%22%7D; Hm_lpvt_9793f42b498361373512340937deb2a0=1657700275"
    })
    print(res.json())
    

    cookie登录古诗文网

    import requests
    from bs4 import BeautifulSoup
    import urllib.request
    
    # https://so.gushiwen.cn/user/login.aspx?from=http%3a%2f%2fso.gushiwen.cn%2fuser%2fcollect.aspx
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
    }
    
    login_url = 'https://so.gushiwen.cn/user/login.aspx?from=http://so.gushiwen.cn/user/collect.aspx'
    response = requests.get(url=login_url, headers=headers)
    content = response.text
    
    # 解析页面源码,分别获取 __VIEWSTATE   __VIEWSTATEGENERATOR
    soup = BeautifulSoup(content, 'lxml')
    viewstate = soup.select('#__VIEWSTATE')[0].attrs.get('value')
    viewstategenerator = soup.select('#__VIEWSTATEGENERATOR')[0].attrs.get('value')
    
    # 获取验证码图片并保存
    img = soup.select('#imgCode')[0].attrs.get('src')
    code_url = 'https://so.gushiwen.cn/' + img
    
    # requests里有一个方法 session(),通过session的返回值,就能使请求变成一个对象
    # urllib.request.urlretrieve(code_url, '../../../data/code.jpg')
    session = requests.session()
    response_code = session.get(code_url)
    # 图片下载需要使用二进制下载
    content_code = response_code.content
    with open('../../../data/code.jpg', 'wb') as f:
        f.write(content_code)
    
    code_name = input('请输入验证码:')
    
    # 点击登录接口
    url_post = 'https://so.gushiwen.cn/user/login.aspx?from=http%3a%2f%2fso.gushiwen.cn%2fuser%2fcollect.aspx'
    data = {
        '__VIEWSTATE': viewstate,
        '__VIEWSTATEGENERATOR': viewstategenerator,
        'from': 'http://so.gushiwen.cn/user/collect.aspx',
        'email': 'xxxxxxxxx',
        'pwd': '123123',
        'code': code_name,
        'denglu': '登录',
    }
    
    response_post = session.post(url=url_post, headers=headers, data=data)
    content_post = response_post.text
    with open('../../../data/古诗文.html', 'w', encoding='utf-8') as f:
        f.write(content_post)
    
    

    4.2代理

    破解封IP的各种反扒机制

    • 社么是代理:
      1. 代理服务器
    • 代理作用:
      * 突破自身IP访问的限制
      * 可以隐藏自身真实IP
    • 代理相关网站:
      • 快代理
        • www.goubanjia.com
          • 西祠代理
    • 代理IP类型:
      * http:应用到http协议对应的URL中
      * https:应用到https协议对应的URL中
    • 代理IP的匿名度:
      • 透明:服务器知道该次请求使用了代理, 也知道请求对应的真实IP
      • 匿名:知道使用代理,不知道真实IP
      • 高匿名:不知道使用代理,更不知道真实IP

    案例

    requests

    import requests
    
    
    proxies={
        #"http":""
        "https":"222.110.147.50:3218"  #代理IP地址
    }
    res=requests.get("https://www.baidu.com/s?tn=87135040_1_oem_dg&ie=utf-8&wd=ip",proxies=proxies)
    res.encoding='utf-8'
    
    with open('ip.html','w') as fp:
        fp.write(res.text)
    

    urllib.request

    import urllib.request
    
    url='https://www.baidu.com/s?ie=UTF-8&wd=ip'
    headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
        }
    request=urllib.request.Request(url=url,headers=headers)
    proxies={
       'http': '118.24.219.151:16817'
    }
    #模拟浏览器访问服务器
    handler=urllib.request.ProxyHandler(proxies=proxies)
    opener=urllib.request.build_opener(handler)
    resposne=opener.open(request)
    
    #获取相应信息
    content=resposne.read().decode('utf-8')
    with open('../data/代理.html','w',encoding='utf-8') as f:
        f.write(content)
    

    4.3防盗链

    在访问一些链接的时,网站会进行溯源

    他所呈现的反扒核心:当网站中的一些地址被访问的时候,会溯源到你的上一个链接
    需要具备的一些“环境因素”,例如访问的过程中需要请求的时候携带headers

    案例:梨视频

    #分析
    #请求返回的地址 和 可直接观看的视频的地址有差异#
    #请求返回的数据中拼接了返回的systemTime值
    #真实可看的视频地址中有浏览器地址栏的ID值
    #解决办法,将url_no中的systemTime值替换为cont-拼接的ID值
    #url='https://www.pearvideo.com/video_1767372
    
    import requests,os
    
    if not os.path.exists('./网络爬虫/sucai/梨video'):
            os.makedirs('./网络爬虫/sucai/梨video')
    
    
    #梨视频爬取视频
    url='https://www.pearvideo.com/video_1767372'
    contId= url.split('_')[1]
    headers={
        "User-Agent":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36",
        "Referer":url,
    }
    videoStatusUrl=f'https://www.pearvideo.com/videoStatus.jsp?contId={contId}&mrd=0.6004271686556242'
    res =requests.get(videoStatusUrl,headers=headers)
    #print(res.json())
    dic=res.json()
    srcUrl=dic['videoInfo']['videos']['srcUrl']
    systemTime=dic['systemTime']
    srcUrl=srcUrl.replace(systemTime,f'cont-{contId}')
    #下载视频,进行存储
    video_data=requests.get(url=srcUrl,headers=headers).content
    with open('./网络爬虫/sucai/梨video/video.mp4','wb')as fp:
        fp.write(video_data)
    

    (五)异步爬虫

    目的:在爬虫中使用异步实现高性能的数据爬取操作

    • 进程是资源单位,每一个进程至少要有一个线程
    • 线程是执行单位
    • 同一个进程中的线程可共享该进程的资源
    • 启动每一个城西默认都会有一个主线程

    异步爬虫方式:

    1. 多线程,多进程:(不建议)

      • 好处:可以被相关阻塞的操作单独开启线程或者进程,阻塞操作就可以异步执行
      • 弊端:无法无限开启多线程或者多进程。
    2. 进程池,线程池(适当使用)

      • 好处:可以降低系统对进程或者线程创建和销毁一个频率,从而很好的降低系统的开销
      • 弊端:池中线程或者进程的数量有上限
      • 线程池

    一次性开辟一些线程,我们用户直接给线程池提交任务,线程任务的调度交给线程池来完成

    1. 单线程+异步协程
    • event_loop:事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上
      当满足某些条件的时候,函数就会被循环执行
    • coroutine:协程对象,我们可以将协程对象注册到事件循环中,它会被事件循环调用。
      我们可以使用 async关键字来定义一个方法,这个方法在调用时不会立即执行,而是返回一个协程对象
    • task:任务,它是对协程对象的进一步封装,包含了任务的各个状态,
    • future:代表将来执行或还没有执行的任务,实际上和task没有本质区别
    • async:定义一个协程
    • await用来挂起阻塞方法的执行

    5.0单线程

    headers={
        "User-Agent":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36"
    }
    
    urls=[
        'https://www.51kim.com/'
        'https://www.baidu.com/'
          'https://www.baidu.com/'
    ]
    

    5.1多线程

    from threading import Thread  #线程类
    
    def fun():
        for i in range(100):
            print('func',i)
    
    
    if __name__ =='__main__':
        t=Thread(target=fun) #创建线程,为线程安排任务
        t.start()  #多线程状态为可以开始工作的状态,具体执行时间由CPU决定
    
        for i in range(100):
            print('main',i)
            
    '''
    def MyThread(Thread):
        def run(self):   #当线程被执行的时候,被执行的就是run
            for i in range(1000):
                print("子线程",i)
    if __name__ =='__main__':
        t=MyThread()
        t.start()#开启线程
        for i in range(1000):
            print("主线程",i)
    
    '''
    
    def fun(name):
        for i in range(1000):
            print(name,i)
    
    
    if __name__ =='__main__':
        #给进程起名字,分得清楚是哪个进程
        t1=Thread(target=fun,args=('周杰伦',)) #传递参数必须是元组
        t1.start()  
    
        t2=Thread(target=fun,args=('周星驰',)) 
        t2.start()  
    

    5.2多进程

    from multiprocessing import Process
    
    
    
    def fun():
        for i in range(10000):
            print('子进程',i)
    
     
    if __name__ =='__main__':
        p=Process(target=fun) 
        p.start() 
    
        for i in range(10000):
            print('主进程',i)
    

    案例:基于进程+线程实现多任务爬虫

    需求分析

    网络爬虫集合【看这一篇就够了】_第3张图片

    import uuid
    from multiprocessing import Queue, Process
    from threading import Thread
    
    import pymysql
    from lxml import etree
    import requests
    
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
    }
    
    
    class DownloadThread(Thread):
        def __init__(self, url):
            super().__init__()
            self.url = url
    
        def run(self):
            print('开始下载', self.url)
            resp = requests.get(url=self.url, headers=headers)
            if resp.status_code == 200:
                resp.encoding = 'utf-8'
                self.content = resp.text
            print(self.url, '下载完成')
    
        def get_content(self):
            return self.content
    
    
    class DownloadProcess(Process):
        """
        下载进程
        """
    
        def __init__(self, url_q, html_q):
            self.url_q: Queue = url_q
            self.html_q = html_q
            super().__init__()
    
        def run(self):
            while True:
                try:
                    url = self.url_q.get(timeout=30)
                    # 将下载任务交给子线程
                    t = DownloadThread(url)
                    t.start()
                    t.join()
                    # 获取下载的数据
                    html = t.get_content()
    
                    # 将数据压入到解析队列中
                    self.html_q.put((url, html))
    
                except:
                    break
            print('---下载进程over---')
    
    
    class ParseThread(Thread):
        def __init__(self, html, url_q):
            self.html = html
            self.url_q = url_q
            super().__init__()
    
        def run(self):
            tree = etree.HTML(self.html)
            imgs = tree.xpath('//div[contains(@class,"com-img-txt-list")]//img')
    
            for img in imgs:
                item = {}
                item['id'] = uuid.uuid4().hex
                item['name'] = img.xpath('./@data-original')[0]
                item['src'] = img.xpath('./@alt')[0]
                # 将item数据写入数据库
                conn = pymysql.connect(
                    user='root',
                    password='123.com',
                    host='127.0.0.1',
                    port=3306,
                    database='dog',
                    charset='utf8'
                )
                cursor = conn.cursor()
                sql = 'insert into labuladuo(name,src) values("{}","{}")'.format(item['name'], item['src'])
                cursor.execute(sql)
                conn.commit()
                cursor.close()
                conn.close()
    
                print(item)
            # 获取下一页的链接
            next_page = tree.xpath('//a[@class="nextpage"]/@href')
            if next_page:
                next_url = 'https://sc.chinaz.com/tupian/' + next_page[0]
                self.url_q.put(next_url)  # 将新的下载任务放到下载队列中
    
    
    class ParseProcess(Process):
        """
        解析进程
        """
    
        def __init__(self, url_q, html_q):
            super().__init__()
            self.url_q = url_q
            self.html_q = html_q
    
        def run(self):
            while True:
                try:
                    # 读取解析的任务
                    url, html = self.html_q.get(timeout=60)
                    # 启动解析线程
                    print('开始解析', url)
                    t = ParseThread(html, self.url_q).start()
    
    
                except:
                    break
            print('---解析进程over---')
    
    
    if __name__ == '__main__':
        task1 = Queue()  # 下载任务队列
        task2 = Queue()  # 解析任务队列
        # 起始爬虫任务
        task1.put('https://sc.chinaz.com/tupian/labuladuo.html')
        p1 = DownloadProcess(task1, task2)
        p2 = ParseProcess(task1, task2)
        p1.start()
        p2.start()
        p1.join()
        p2.join()
        print('Over !!!!')
    

    5.3线程池和进程池

    from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
    
    
    def fun(name):
        for i in range(1000):
            print(name,i)
    if __name__ =='__main__':
        #创建线程池
        with ThreadPoolExecutor(50) as t:
            for i in range(100):
                t.submit(fun,name=f'线程{i}')
    
        #等待线程池中的任务全部执行完毕,才继续执行
        print('123132')
    
    '''
    def fun(name):
        for i in range(1000):
            print(name,i)
    if __name__ =='__main__':
        #创建进程池
        with ProcessPoolExecutor(50) as t:
            for i in range(100):
                t.submit(fun,name=f'进程{i}')
    
        #等待线程池中的任务全部执行完毕,才继续执行
        print('123132')
        '''
    

    案例:水果交易网

    #https://www.guo68.com/sell?page=1中国水果交易市场
    
    #先考虑如何爬取单个页面的数据
    #再用线程池,多页面爬取
    import requests,csv,os
    from fake_useragent import UserAgent
    from concurrent.futures import ThreadPoolExecutor
    from lxml import etree
    
    
    if not os.path.exists('./网络爬虫/sucai/中国水果交易'):
        os.makedirs('./网络爬虫/sucai/中国水果交易')
    f='./网络爬虫/sucai/中国水果交易/fruit.csv'
    fp=open(f,'w',encoding='utf-8')
    csvwrite=csv.writer(fp)
    
    
    def download_one_page(url):
        #拿去页面代码
        response=requests.get(url=url,headers={"user-agent":UserAgent().chrome}).text
        #解析数据 
        tree=etree.HTML(response)
        list_data=tree.xpath('//li[@class="fruit"]/a')
        all_list_data=[]
        for i in list_data:
            list_price=i.xpath('./div[2]/span[1]/text()')[0]
            list_sort=i.xpath('./p[1]/text()')[0]
            list_address=i.xpath('./p[2]/text()')[0]
            all_list_data.append([list_price,list_sort,list_address])
            #print(list_price,list_address,list_sort)
        #持久化存储
        csvwrite.writerow(all_list_data)
        print(url,'下载完毕')
    
    if __name__ =='__main__':
        with ThreadPoolExecutor(50) as Thread: #使用线程池,池子里放50个线程
            for i in range(1,60):
                Thread.submit(download_one_page,f'https://www.guo68.com/sell?page={i} ')
        print("全部下载完毕")
    

    5.4协程

    • 协程不是计算机真实存在的(计算机只有进程,线程),而是由程序员人为创造出来的。

    • 协程也可以被成为微线程,是一种用户态内的上下文切换技术,简而言之,其实就是通过一个线程实现代码块相互切换执行

    • 实现协程方法:

      • 通过greenlet,早期模块

      • yield关键字

      • asyncio模块

      • async,await关键字 【推荐】

    • 协程意义

    • 在一个线程中如果遇到IO等待时间,线程不会傻傻等,利用空闲的时间再去干点别的事

    import time 
    def fun():
        print('*'*50)
        time.sleep(3)#让当前线程处于阻塞状态,CPU是不为我工作的
        print('*****'*10)
    
    #input()  程序也是处于阻塞状态
    # requests.get(bilibili)在网络请求返回数据之前,程序也是处于阻塞状态
    # 一般情况下,当程序处于 IO操作的时候,线程都会处于阻塞状态
    
    # 协程:当程序遇见IO操作的时候,可以选择性的切换到其他任务上
    # 在微观上是一个任务一个任务的切换进行,切换条件一般是IO操作
    # 宏观上,我们能看到的其实就是多个任务一起在执行
    # 多任务异步操作
    
    # 上方所讲都是在单线程条件下
    
    if __name__ =='__main__':
        fun()
    
    #Python 协程
    import asyncio
    import time
    '''
    async def fun1():
        print("你好,我叫周星驰")
        time.sleep(3)  #当程序出现同步操作的时候,异步就中断了,requests.get
        await asyncio.sleep(3)  #异步操作代码
        print('我叫周星驰')
    
    async def fun2():
        print("你好,我叫周杰伦")
        time.sleep(2)
        await asyncio.sleep(2)
        print('我叫周杰伦')
    
    
    async def fun3():
        print("你好,我叫周星驰")
        time.sleep(4)
        await asyncio.sleep(4)
        print('我叫小潘')
    
    
    if __name__ =="__main__":
    
        f1 =fun1()#此时的函数是异步协程函数,此时函数执行得到的是一个协程对象
        #print(g)
        #asyncio.run(g)  #协程需要asyncio模块的支持
        f2=fun2()
        f3=fun3()
        task=[
            f1,f2,f3
        ]
        t1=time.time()
        #一次性启动多个任务(协程)
        asyncio.run(asyncio.wait(task))
        t2=time.time()
        print(t2-t1)
    '''
    
    async def fun1():
        print("你好,我叫周星驰")  
        await asyncio.sleep(3)  #异步操作代码
        print('我叫周星驰')
    
    async def fun2():
        print("你好,我叫周杰伦")
       
        await asyncio.sleep(2)
        print('我叫周杰伦')
    
    
    async def fun3():
        print("你好,我叫周星驰")
       
        await asyncio.sleep(4)
        print('我叫小潘')
    
    
    async def main():
        #第一种写法
        #fi=fun1()
        #await f1  #一般await挂起操作放在协程对象前面
    
        #第二种写法
        task=[
    
            asyncio.create_task(fun1()), #py3.8版本以后加上asyncio.create_task()
            asyncio.create_task(fun2()),
            asyncio.create_task(fun3())
        ]
        await asyncio.wait(task)
    if __name__ =="__main__":
        t1=time.time()
        asyncio.run(main())
        t2=time.time()
        print(t2-t1)
        
    
    5.4.1async & await关键字

    python3.4之后版本能用

    import asyncio
    
    async def fun1():
        print(1)
        await asyncio.sleep(2)#遇到IO操作时,自动切换到task中的其他任务
        print(2)
    async def fun2():
        print(3)
        await asyncio.sleep(3)#遇到IO操作时,自动切换到task中的其他任务
        print(4)
    
    task=[
        asyncio.ensure_future(fun1())
        asyncio.ensure_future(fun2())
    ]
    loop= asyncio.get_event_loop()
    loop.run_untile_complete(asyncio.wait(task))
    #遇到IO阻塞自动切换
    
    
    5.4.2事件循环

    理解为一个死循环,检测并执行某些代码

    import asyncio
    #生成或获取一个事件循环
    loop=asyncio.get_event_loop()
    #将任务放到‘任务列表’
    loop.run_untile_complete(任务)
    
    5.4.3快速上手

    协程函数:定义函数时,async def 函数名
    协程对象:执行 协程函数(),得到的就是协程对象

    async def func():
        pass
    
    result=func()
    
    

    执行协程函数创建协程对象,函数内部代码不会执行
    如果想要执行协程函数内部代码,必须要将协程对象交给事件循环来处理

    import asyncio
    
    async def func():
        pritn("你好,中国)
    
    result=func()
    
    #loop=asyncio.get_event_loop()
    #loop.run_untile_complete(result)
    
    asyncio.run(result)   
    
    5.4.4await关键字

    await +可等待对象(协程对象,task对象,Future对象=====》IO等待)

    示例1:

    import asyncio
    async def fun():
        print("12374")
        await asyncio.sleep(2)
        print("结束")
    
    asyncio.run(fun())
    

    示例2:

    import asyncio
    async def others():
        print('start')
        await asyncio.sleep(2)
        print('end")
        return '返回值'
    
    async def fun():
        print("执行协程函数内部代码")
        #遇到IO操作 挂起当前协程(任务),等IO操作完成之后再继续往下执行,当前协程挂起时,事件循环可以去执行其他些协程(任务)
        response =await others()
    
        print('IO请求结束,结果为:',response)
    
    
    asyncio.run(fun())
    
    

    示例3:

    import asyncio
    async def others():
        print('start')
        await asyncio.sleep(2)
        print('end")
        return '返回值'
    
    async def fun():
        print("执行协程函数内部代码")
        #遇到IO操作 挂起当前协程(任务),等IO操作完成之后再继续往下执行,当前协程挂起时,事件循环可以去执行其他些协程(任务)
        response1 =await others()
        print('IO请求结束,结果为:',response1)
    
    
        response2 =await others()
        print('IO请求结束,结果为:',response2)
    asyncio.run(fun())
    
    

    await 等待对象的值得到结果之后才继续往下走

    5.4.5Task对象

    白话:在事件循环中添加多个任务
    Task用于并发调度协程,通asyncio.create_task(协程对象)的方式创建Task对象,这样可以让协程加入事件循环中等待被调度执行,除了使用asyncio.create_task()函数以外,还可以用低级的loop.create_task()或者ensure_future()函数。不建议手动实例化Task对象

    示例1:

    import asyncio
    
    async def func():
        print(1)
        await asyncio.sleep(2)
        print(2)
        return '返回值'
    async def main():
        print("main开始")
    
        #创建Task对象,将当前执行func()函数任务添加到事件循环中
        task1=asyncio.create_task(func())
        #创建Task对象,将当前执行func()函数任务添加到事件循环中
        task2=asyncio.creat_task(func())
    
        print("main结束")
        #当执行某协程遇到IO操作时,会自动化切换执行其他任务
        #此处的await是等待相对应的协程全部都执行完毕并获取结果
        res1=await task1
        res2=await task2
        print(res1,res2)
    
    asyncio.run(main())
    

    示例2:

    import asyncio
    
    async def func():
        print(1)
        await asyncio.sleep(2)
        print(2)
        return '返回值'
    async def main():
        print("main开始")
    
        task_list=[
        asyncio.create_task(func(),name="n1")
        asyncio.create_task(func(),name="n2")
    ]
        print("main结束")
       #返回值会放到done里面,
        done,pending=await asyncio.wait(task_list,timeout=None)
        print(done)
    
    asyncio.run(main())
    

    示例3:

    import asyncio
    
    async def func():
        print(1)
        await asyncio.sleep(2)
        print(2)
        return '返回值'
    
    
    task_list=[
            func(),
            func(),
    ]
        
    
    done,pending=asyncio.run( asyncio.wait(task_list))
    print(done)
    
    5.4.6async.Future对象

    Task继承Tuture对象,Task对象内部await结果的处理基于Future对象来的
    示例1:

    async def main():
        #获取当前事件循环
        loop=asyancio.get_running_loop()
    
        #创建一个任务(Future对象)这个任务什么都不干
        fut=loop.create_future()
    
        #等待任务最终结果(Future对象),没有结果则会一直等下去
        await fut
    
    asyncio.run(main())
    

    示例2:

    async def set_after():
        await asyncio.sleep(2)
        fut.set_result("666")
    
    async def main():
        #获取当前事件循环
        loop=asyancio.get_running_loop()
    
        #创建一个任务(Future对象)没绑定任何行为,则这个任务永远不知道社么时候结束
        fut=loop.create_future()
    
        #创建一个任务(Task对象)绑定了set_after函数,函数内部在2s后,会给fut赋值
        #手动设置future任务的最终结果,那么fut就可以结束了
        await loop.create_task(set_after(fut))
      
        #等待 Future对象获取 最终结果,否则一直等下去
        data=await fut
        print(data)
    asyncio.run(main())
    
    
    5.4.7concurrent.futures.Future对象

    使用线程池,进程池来实现异步操作时用到的对象

    import time 
    from concurrent.futures.thread import ThreadPoolExecutor
    from concurrent.futures.process import ProcessPoolExecutor
    from concurrent.futures import Future
    
    def fun(value):
        time.sleep(1)
        print(value)
    pool=ThreadPoolExecutor(max_workers=5)
    #或者pool=ProcessPoolExecutor(max_workers=5)
    
    for i in range(10):
        fut =pool.submit(fun,i)
        print(fut)
    
    5.4.8异步和非异步

    案例:asyncio + 不支持异步的模块

    import requests
    import asyncio
    
    async def down_load(url):
        #发送网络请求,下载图片,(遇到网络下载图片的IO请求,自动化切换到其他任务)
        print('开始下载',url)
    
        loop=asyncio.get_event_loop()
        #requests模块默认不支持异步操作,所以使用线程池来配合实现了
        future=loop.run_in_executor(None,requets.get,url)
    
        response=await future
        print("下载完成")
        #图片保存本地
        file_name=url.rsplit('/')[-1]
        with open(file_name,'wwb') as fp:
            fp.write(response.content)
    if __name__=="__main__":
        urls=[
            ""
            ""
            ""
        ]
        tasks=[ down_load(url) for url in urls]+
        loop=asyncio.get_event_loop()
        loop.run_until_complete(asyncio.wait(tasks))
    
    5.4.9异步上下文管理器

    5.5 多任务异步协程

    import asyncio,time
    
    
    async def request(url):
        print("正在下载:",url)
        await asyncio.sleep(2)
        print('下载完毕!!!',url)
    strat_time=time.time()
    urls=[
        "www.baidui.com",
        "www.sougou.com",
        "www.goubanjai.com"
    ]
    tasks=[]
    for url in urls:
        c =request(url)
        task=asyncio.ensure_future(c)
        tasks.append(task)
    
    loop=asyncio.get_event_loop()
    #需要将任务列表封装到waith中
    loop.run_until_complete(asyncio.wait(tasks))
    
    print(time.time()-strat_time)
    

    5.6aiohttp模块

    #requests.get()#同步代码,——>异步操作aiohttp
    #pip install aiohttp
    
    import asyncio
    import aiohttp
    import aiofiles
    urls=[
            "http://kr.shanghai-jiuxin.com/file/2020/0605/819629acd1dcba1bb333e5182af64449.jpg",
            "http://kr.shanghai-jiuxin.com/file/2022/0515/small287e660a199d012bd44cd041e8483361.jpg",
            "http://kr.shanghai-jiuxin.com/file/2022/0711/small72107f2f2a97bd4affd9c52af09d5012.jpg"
        ]
    
    async def aiodownload(url):
       # s = aiohttp.ClientSession()  《=====》requests
        #requests.get .post
        #s.get  s.post
        img_name=url.split('/')[-1]
        async with aiohttp.ClientSession() as session:
            data=await session.get(url)
            async with aiofiles.open(img_name,'wb') as fp:
                fp.write(data) 
           # with open(img_name,'wb') as  fp:
                #fp.write(await data.content.read())#读取内容是异步的,需要await挂起
        print(img_name,'搞定')
    
    
    async def main():
        tasks=[]
        for url in urls:
            tasks.append(aiodownload(url))
        await asyncio.wait(tasks)
    
    
        '''
        tasks=[]
        for url in urls
            task_obj=asyncio.ensure_future(download(url))
            tasks.append(task_obj)
        
        await asyncio.wait(tasks)
        '''
    if __name__=="__main__":
        asyncio.run(main())
        #loop=asyncio.get_event_loop()
        #loop.run_until_complete(main())
    

    (六)动态加载数据处理

    什么是selenium?
    	* (1) Selenium是一个用于web应用程序测试的工具。
    	* (2) Selenium测试直接运行在浏览器中,就像滇正的用户在操作一样。1
    	* (3) 支持通过各种driver(FirfoxDriver,IternetExplorerDriver,OperaDriver,ChromeDriver)驱动
      真实浏览器完成测试
    	* (4) seleniumt也是支持无界面浏览器操作的。
    
    为什么使用selenium?
    	模拟浏览器功能,自动执行网页中的js代码,实现动态加载
    

    selenium

    6.0selenium模块使用
    • 便捷获取网站动态加载的数据
    • 实现模拟登陆

    selenium是基于浏览器自动化的一个模块
    使用流程

    • 先打开chrome 输入 chrome://version/来查看chrome版本

    chromedriver驱动,,驱动下载好放在当前目录下就可以了

    查看驱动和浏览器版本的映射关系

    chromedriver官方文档

    浏览迷:各种浏览器版本

    安装selenium

    pip install selenium
    

    实例化一个浏览器对象:

    from selenium import webdriver
    #实例化一个浏览器对象,(传入浏览器的驱动生成)
    bro = webdriver.Chrome(executable_path = './chromedriver')
    

    方法


    #发起请求 get(url)
    #标签定位 find_element  新版本需要导入from selenium.webdriver.common.by import By
    #标签交互(搜索)send_keys('xxxx')
    #点击  click()
    #获取网页代码 page_source
    #执行js程序 excute_script(JS代码)
    #前进,后退  fowawrd()  back()
    #关闭浏览器  quit()
    #对当前页面进行截图裁剪  保存, bro.save_screenshot('a.jpg')
    

    获取文本和标签属性的方法

    # driver: 是之前定义的打开浏览器的 “变量名称”
    # .text: 是获取该标签位置的文本
    # .get_attribute(value).:获取标签属性
    # value:属性字段名
    

    使用获取红楼梦章节标题

    from selenium import webdriver
    from lxml import etree
    from time import sleep
    #实例化浏览器驱动对象,
    drive = webdriver.Chrome()
    #让浏览器发送一个指定url请求
    drive.get("https://www.shicimingju.com/book/hongloumeng.html")
    #page_source获取浏览器当前页面的页面代码,(经过数据加载以及JS执行之后的结果的html结果),
    #我们平时打开页面源代码看得数据 和  点击检查在elements中看得代码是不一样的,有些是经过动态加载,在页面源代码代码里面不显示
    page_text=drive.page_source
    
    #解析红楼梦章节标题
    tree=etree.HTML(page_text)
    list_data=tree.xpath('//div[@class="book-mulu"]/ul/li')
    for i in list_data:
        article_title=i.xpath('./a/text()')[0]
        print(article_title)
    
    sleep(5)
    #关闭浏览器
    drive.quit()
    
    6.1其他自动化操作
    selenium的元素定位?
        元素定位:自动化要做的就是模拟鼠标和键盘来操作来操作这些元素,点击、输入等等。操作这些元素前首先要找到它们,WebDriver提供很多定位元素的方法
    

    网络爬虫集合【看这一篇就够了】_第4张图片

    访问元素信息
        获取元素属性
        	.get_attribute('class')
        获取元素文本
        	text
        获取id
        	.id
        获取标签名
        	.tag_name
    
    #访问元素信息
    from selenium import webdriver
    from selenium.webdriver.common.by import By
    
    browser = webdriver.Chrome('chromedriver.exe')
    
    browser.get('https://www.baidu.com')
    
    input = browser.find_element(by=By.ID, value='su')
    print(input.get_attribute('class'))  # 属性值
    print(input.tag_name)  # 标签名字
    a=browser.find_element(by=By.LINK_TEXT,value='新闻')
    print(a.text)
    
    #访问交互
    from selenium import webdriver
    from time import  sleep
    from selenium.webdriver.common.by import By
    
    drive=webdriver.Chrome()
    drive.get('https://www.taobao.com/')
    # print(drive.title)
    
    #标签定位
    search_input =drive.find_element(By.ID,'q')
    #标签交互
    search_input.send_keys('Iphone ')
    
    #执行一组js程序
    drive.execute_script("window.scrollTo(0,document.body.scrollHeight)")
    sleep(3)
    #点击搜索按钮
    bottom = drive.find_element(By.CSS_SELECTOR,'.btn-search')
    bottom.click()
    
    #在访问一个url
    drive.get('http://www.baidu.com')
    sleep(2)
    #回退
    drive.back()
    sleep(2)
    #前进
    drive.forward()
    sleep(2)
    # 关闭浏览器
    sleep(4)
    drive.quit()
    
    6.2拉钩网自动化操作
    from selenium.webdriver import Chrome
    import  time
    from selenium.webdriver.common.keys import Keys
    from selenium.webdriver.common.by import By
    
    
    
    drive = Chrome()
    drive.get('https://www.lagou.com/')
    
    #点击全国
    quanguo=drive.find_element(by=By.XPATH,value='//*[@id="changeCityBox"]/p[1]/a')
    quanguo.click()
    #定位到搜索框, 输入python, 输入回车/点击搜索按钮
    drive.find_element(by=By.XPATH,value='//*[@id="search_input"]').send_keys('Python',Keys.ENTER)
    
    #得到职位,薪资,公司名称
    div_list=drive.find_elements(by=By.XPATH,value='//*[@id="jobList"]/div[1]/div')
    # print(div_list)
    for i in div_list:
        Job_name=i.find_element(by=By.XPATH,value='./div[1]//a').text
        Job_price=i.find_element(by=By.XPATH,value='./div[1]//span').text
        company_name=i.find_element(by=By.XPATH,value='./div[1]/div[2]//a').text
        print(Job_name,Job_price,company_name)
    
    time.sleep(2)
    drive.quit()
    
    6.3窗口切换
    from selenium.webdriver import Chrome
    import time
    from selenium.webdriver.common.keys import Keys
    from selenium.webdriver.common.by import By
    
    drive = Chrome()
    drive.get('https://www.lagou.com/')
    
    # 点击全国
    drive.find_element(by=By.XPATH, value='//*[@id="changeCityBox"]/p[1]/a').click()
    time.sleep(1)
    
    # 定位到搜索框, 输入python, 输入回车/点击搜索按钮
    drive.find_element(by=By.XPATH, value='//*[@id="search_input"]').send_keys('Python', Keys.ENTER)
    time.sleep(1)
    
    drive.find_element(by=By.XPATH, value='//*[@id="jobList"]/div[1]/div[1]/div[1]/div[1]/div[1]/a').click()
    
    #如何进入到新窗口中进行提取
    # 在selenium眼中,新窗口默认是不切换过来的
    drive.switch_to.window(drive.window_handles[-1])
    
    #在新窗口中提取内容
    job_detail = drive.find_element(by=By.XPATH,value='//*[@id="job_detail"]/dd[2]').text
    print(job_detail)
    
    #关闭子窗口中
    drive.close()
    
    #变更窗口视角,回到原来的窗口
    drive.switch_to.window(drive.window_handles[0])
    
    print(drive.find_element(by=By.XPATH, value='//*[@id="jobList"]/div[1]/div[1]/div[1]/div[1]/div[1]/a').text)
    
    time.sleep(2)
    drive.quit()
    
    6.4动作链和iframe的处理
    from selenium.webdriver import Chrome
    from selenium.webdriver.common.by import By
    # 导入动作链对应的类
    from selenium.webdriver import ActionChains
    from time import sleep
    
    bro = Chrome()
    bro.get('')
    # 如果定位的标签是存在于iframe标签中的则必须通过如下操作进行标签定位
    bro.switch_to.frame(id)  # 切换浏览器标签定位的作用域
    
    div = bro.find_element(By.ID, 'draggable')
    
    #从iframe回到原页面用default_content()
    # bro.switch_to.default_content() #切换到原页面
    
    
    #动作链
    action =ActionChains(bro)
    #点击长按指定的标签
    action.click_and_hold(div)#长按,
    
    for i in range(5):
        #perform()立即执行动作链操作
        #move_by_offset(x,y)x表示水平方向,y表示竖直方向
        action.move_by_offset(17,0).perform()#移动
        sleep(0.3)
    #释放动作链
    action.release()
    
    #关闭浏览器
    bro.quit()
    
    6.5模拟登录qq空间
    from  selenium.webdriver import Chrome
    from selenium.webdriver.common.by import By
    from time import sleep
    from selenium.webdriver import ActionChains
    
    #创建浏览器对象
    bro =Chrome()
    #发送请求
    bro.get('https://qzone.qq.com/')
    
    #定位到密码登录标签,遇到iframe切换作用域
    #切换作用域,定位标签
    bro.switch_to.frame('login_frame')
    a_tag=bro.find_element(By.ID,'switcher_plogin')
    a_tag.click()
    
    #定位输入账户的标签
    username = bro.find_element(By.ID,'u')
    #定位到输入密码的标签
    password = bro.find_element(By.ID,'p')
    #输入账户
    username.send_keys('318129549')
    sleep(1)
    #输入密码
    password.send_keys('song027..')
    sleep(1)
    #定位 登录 标签
    login= bro.find_element(By.ID,'login_button')
    #点击
    login.click()
    sleep(3)
    
    bro.quit()
    

    Phantomjs

    1.什么是Phantomjs?
        (1)是一个无界面的浏览器
        (2)支持页面元素查找,js的执行等
        (3)由于不进行css和gui渲染,运行效率要比真实的浏览器要快很多
    
    2.如何使用Phantomjs?
    #除了引用的文件不一样,其他的跟selenium一致
        (1)获取Phantoms.exe文件路径path
        (2)browser=webdriver.PhantomJs(path)
        (3)browser.get(url)
    

    selenium新版本已经不能导入Phantomjs了

    知道有这个东西就OK

    Chrome handless

    固定配置
    # 固定配置
    from selenium.webdriver import Chrome
    #实现无可视化界面
    from selenium.webdriver.chrome.options import Options
    
    # 创建一个参数对象,用来控制chrome以无界面模式打开
    chrome_options = Options()
    chrome_options.add_argument('--headless')
    chrome_options.add_argument('--dissable-gpu')
    
    path=r'C:\Users\456\AppData\Local\Google\Chrome\Application\chrome.exe'
    chrome_options.binary_location=path
    #path是自己电脑谷歌浏览器的位置
    browser = Chrome(chrome_options=chrome_options)
    
    代码封装
    # 代码封装
    from selenium.webdriver import Chrome
    from selenium.webdriver.chrome.options import Options
    def share_browser():
        # 创建一个参数对象,用来控制chrome以无界面模式打开
        chrome_options = Options()
        chrome_options.add_argument('--headless')
        chrome_options.add_argument('--dissable-gpu')
    
        path=r'C:\Users\31812\AppData\Local\Google\Chrome\Application\chrome.exe'
        chrome_options.binary_location=path
    
        browser= Chrome(chrome_options=chrome_options)
        return browser
    
    browser=share_browser()
    url='https://www.baidu.com'
    browser.get(url)
    browser.save_screenshot('baidu.png')
    
    谷歌无头浏览器+反检测
    # 无可视化界面(无头浏览器) 
    
    from selenium.webdriver import Chrome
    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('--dissable-gpu')
    #实现规避检测
    options=ChromeOptions()
    options.add_experimental_option('excludeSwitches',['enable-outomation'])
    
    bro = Chrome(chrome_options=chrome_options,options=options)
    
    # 上网
    bro.get('https://www.baidu.com')
    print(bro.page_source)
    sleep(2)
    
    # 关闭浏览器
    bro.quit()
    

    (七)scrapy框架

    什么是scrapy?
    	Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。
    

    scrapy创建以及运行

    1.创建scrapyl项目:
    	终端输入	scrapy startproject 项目名称
        #项目名不能以数字开头,不能包含中文
    
    2.项目组成
    	spiders
            init_·py
            自定义的爬虫文件.py		--》由我们自己创建,是实现爬虫核心功能的文件
        __init_.py
        items.py				--》定义数据结构的地方,是一个继承自scrapy.Item的类
        middlewares.py			--》中间件代理
        pipelines.py			--》管道文件,里面只有一个类,用于处理下载数据的后续处理
    								默认是300优先级,值越小优先级越高(1-1000)
    	settings.py				--》配置文件比如:是否遵守robotsi协议,User-Agent定义等
    
    

    网络爬虫集合【看这一篇就够了】_第5张图片

    3.	创建爬虫文件:
            (1)跳转到spiders文件夹	cd 项目名字/项目名字/spiders
            (2)scrapy genspider  爬虫名字	网页的域名
            
    	爬虫文件的基本组成:
    		继承scrapy.Spider类
    				name ='baidu'		---》运行爬虫文件时使用的名字
    				allowed domains		---》爬虫允许的域名,在爬取的时候,如果不是此域名之下的url,会被过滤掉
    				start_urls			--》声明了爬虫的起始地址,可以写多个u1,一般是一个
    				parse(self,response)--》解析数据的回调函数
    							response.text	--》响应的是字符串
                        		response.body	--》响应的是二进制文件
    							response.xpath()--》xpath方法的返回值类型是selector列表
                                extract()		--》提取的是selector对象的data属性值
                                extract_first() --》提取的是selector列表中的第一个数据
                                
    
    4.运行爬虫代码
    	scrapy crawl 爬虫名字     (--nolog)
    

    scrapy架构组成

    (1)引擎		--》自动运行,无需关注,会自动组织所有的请求对象,分发给下载器
    
    (2)下载器		--》从引擎处获取到请求对象后,请求数据
    
    (3)spiders		--》Spider类定义了如何爬取某个(或某些)网站。包括了爬取的动作(例
    如:是否跟进链接)以及如何从网页的内容中提取结构化数据(爬取item)。换句话说,Spideri就是您定义爬取的动作及分析某个网页(或者是有些网页)的地方。
    
    (4)调度器			--》有自己的调度规则,无需关注
    
    (5)管道(Item pipeline)	--》最终处理数据的管道,会预留接口供我们处理数据
    
    当Item在Spider中被收集之后,它将会被传递到虹tem Pipeline,一些组件会按照一定的顺序执行对缸tem的处理。
    每个item pipeline组件(有时称之为“Item Pipeline”)是实现了简单方法的Python类。他们接收到Item并通过它执行一些行为,同时也决定此Item是否继续通过pipeline,或是被丢弃而不再进行处理,
    
        以下是item pipelinef的一些典型应用:
            1.清理HTML数据
            2.验证爬取的数据(检查item包含某些字段)
            3.查重(并丢弃)
            4.将爬取结果保存到数据库中
    
    五大核心组件
    1. 引擎 Scrapy

    用来处理整个系统的数据流处理,触发事物(框架核心)

    1. 调度器 Scheduler

    用来接受引擎发过来的请求,压入队列中,并在引擎再次请求的时候返回,可以想象成一个URL(抓取网页的地址或者说是链接)的优先队列,由它来决定下一个要抓取的网址是什么,同时去除重复的网址

    1. 下载器 Downloader

    用于下载网页内容,并将网页内容返回给的链接(Scrapy下载器是建立在twisted这个高效的异步模型上的)

    1. 爬虫 Spider

    爬虫是主要干活的,用于从特定的网页中提取自己需要的信息,即所有的实体(item)用户也可以从中提取链接,让Scrapy继续抓取下一个页面

    1. 项目管道 Pipeline

    负责处理爬虫从网页中抽取的实体,主要功能是持久化存储,验证实体的有效性,清楚不许需要的信息,当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据

    scrapy工作原理

    网络爬虫集合【看这一篇就够了】_第6张图片

    网络爬虫集合【看这一篇就够了】_第7张图片

    scrapy shell

    1.什么是scrapy she11?
        Scrapy终端,是个交互终端,供您在未启动spider的情况下尝试及调试您的爬取代码。其本意是用来测试提取数据的代码,不过您可以将其作为正常的Python终端,在上面测试任何的Python代码。
        该终端是用来测试XPath或Css表达式,查看他们的工作方式及从爬取的网页中提取的数据。在编写您的spider时,该终端提供了交互性测试您的表达式代码的功能,免去了每次修改后运行spider的麻烦。
        一旦熟悉了Scrapy终端后,您会发现其在开发和调试spider时发挥的巨大作用。
    
    2.安装ipython
        安装:pip install ipython
        简介:如果您安装了IPython,Scrapy终端将使用IPython(替代标准Python终端)。IPython终端与其他相比更为强大,提供智能的自动补全,高亮输出,及其他特性。
    
    
    3.应用:
    	直接打开cmd输入scrapy shell 域名 即可
        (1)scrapy shell www.baidu.com
        (2)scrapy shell http://www.baidu.com
        (3)scrapy shell "http://www.baidu.com"
        (4)scrapy shell "www.baidu.com"
    语法:
        (1)response对象:
            response.body
            response.text
            response.url
            response.status
        (2)response的解析:
            response.xpath()(常用)
            使用xpath路径查询特定元素,返回一个selector列表对象
    

    yield

    1.带有yield的函数不再是一个普通函数,而是一个生成器generator,可用于迭代
    2.yield是一个类似return的关键字,迭代一次遇到yield时就返回yield,后面(右边)的值。重点是:下一次迭代时,从上一次迭代遇到的yield后面的代码(下一行)开始执行
    3.简要理解:yield就是return返回一个值,并且记住这个返回的位置,下次迭代就从这个位置后(下一行)开始
    
    案例:
    	1.当当网		(1)yield  (2).管道封装  (3).多条管道下载  (4)多页数据下载
    	2.电影天堂		(1)  一个item包含多级页面的数据
    

    解析相关

    • selector()

    • css() 样式选择器,返回selector选择器的 可迭代对象

      • scrapy…selector.SelectorList选择器列表
      • scrapy.selector.Selector选择器
      • 样式选择器提取文本或者属性
      ::text 提取文本				lis.css('h4 a::text').get()
      ::attr("属性名") 提取属性    lis.css('img ::attr("src")').get()
      
    • xpath() xpath路径

    • 选择器常用方法

      • css() / xpath()
      • extract() 提取选择中所有内容,返回是Iist
      • extract_first() / get() 提取每个选择器中的内容,返回是文本

    应用

    1.1scrapy持久化存储:
    • 基于终端指令:
      • 要求:只可以将parse方法的返回值存储到本地的文本文件中
      • 要求:持久化存储对应的文本文件类型只可以是:json,jsonlines,jl,csv,xml,
      • 指令:``scrapy crawl SpiderName -o filepath`
      • 缺点:局限性强,(数据只可以存储到指定后缀的文本文件中)
    • 基于管道指令:
      • 编码流程:
          1. 数据解析
          1. 在item类中定义相关的属性
          1. 将解析的数据封装到item 类型的对象
          1. 将item类型对象提交给管道进行持久化存储的操作
          1. 在管道类的process_item中要将其接收到的item对象中存储的数据进行持久化存储操作
          1. 在配置文件中开启管道
      • 优点:通用性强。
    基于本地存储
    基于终端指令存储只可以将parse方法的返回值存储到本地
    scrapy crawl douban -o qidian.csv
    
    import scrapy
    class DoubanSpider(scrapy.Spider):
        name = 'douban.'
        #allowed_domains = ['www.xxx.con']
        start_urls = ['https://www.qidian.com/all/']
    
        def parse(self, response):
            # 解析小说的作者和标题
            list_data=response.xpath('//div[@class="book-img-text"]/ul/li')
            alldata=[]
            for i in list_data:
    
                #xpath返回的是列表,但是列表里一定是Selector类型的对象
                #.extract()可以将Selector对象中的data存储的字符串提取出来
                #列表调用了extract()之后,则表示将列表中的每一个Selector对象中的data对应的字符串提取出来
                #.extract_first()将列表中第0个元素提取出来
                title =i.xpath('./div[2]/h2/a/text()').extract_first()
                author=i.xpath('./div[2]/p/a/text()')[0].extract()
              
    
                 dic={
                     'title':title,
                     'author':author
                 }
                 alldata.append(dic)
             return  alldata
    
    基于管道存储

    使用管道要在``settings.py`中开启管道

    ITEM_PIPELINES = {
        #管道可以有很多个,优先级的范围是1到1000,值越小,优先级越高
       'scrapt_dangdang_03.pipelines.ScraptDangdang03Pipeline': 300,
    }
    

    1.1爬虫文件的编写

    import scrapy
    
    from scrapt_dangdang_03.items import ScraptDangdang03Item
    
    
    class DangSpider(scrapy.Spider):
        name = 'dang'
        allowed_domains = ['category.dangdang.com']
        start_urls = ['http://category.dangdang.com/cp01.01.02.00.00.00.html']
    
        def parse(self, response):
            # src = '//ul[@id="component_59"]/li//img/@src'
            # name='//ul[@id="component_59"]/li//img/@src'
            # price='//ul[@id="component_59"]/li//p[3]/span[1]/text()'
            # 所有的selector对象都可以再次调用xpath方法
    
            li_list = response.xpath('//ul[@id="component_59"]/li')
            for li in li_list:
                # 第一张图片个其他图片的标签的属性是不一样的
                # 第一张图片是src,其他的dadta-original
                src = li.xpath('.//img/@data-original').extract_first()
                if not src:
                    src = li.xpath('.//img/@src').extract_first()
    
                name = li.xpath('.//img/@alt').extract_first()
                price = li.xpath('.//p[3]/span[1]/text()').extract_first()
                print(src, name, price)
    
                item = ScraptDangdang03Item(src=src, name=name, price=price)
                # 获取一个就交给管道
                yield item
    
    

    1.2 item文件的编写

    import scrapy
    
    class ScraptDangdang03Item(scrapy.Item):
        # 通俗将就是要下载的数据都有什么
    
        # 图片
        src = scrapy.Field()
        # 名字
        name = scrapy.Field()
        # 价格
        price = scrapy.Field()
    

    1.3管道文件的编写

    # 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
    
    
    # 要想使用管道必须在settings中开启
    class ScraptDangdang03Pipeline:
        fp = None
    
        def open_spider(self, spider):
            # 在爬虫开始之前就执行的一个方法
            print('开始爬虫'.center(20,'-'))
            self.fp = open('book.json', 'w', encoding='utf-8')
    
        # item就是yield后的item对象
        def process_item(self, item, spider):
            # # 每传递一个对象,就打开一个文件,对文件的操作太过频繁
            # with open('book.json', 'a', encoding='utf-8') as f:
            #     # write必须是一个字符串
            #     # 'w'会覆盖之前的文件内容
            #     f.write(str(item))
            self.fp.write(str(item))
            return item
    
        def close_spider(self, spier):
            print('结束爬虫'.center(20, '-'))
            self.fp.close()
    
    
    
    开启多条管道
    1)定义管道类
    (2)在settings.py中开启管道
    
    #settings.py
    ITEM_PIPELINES = {
        # 管道可以有很多个,优先级的范围是1到1000,值越小,优先级越高 'scrapt_dangdang_03.pipelines.ScraptDangdang03Pipeline': 300,  'scrapt_dangdang_03.pipelines.DangDangDownloadPipeline': 301,
    }
    
    

    案例1

    1.1爬虫文件的编写

    import scrapy
    
    from scrapt_dangdang_03.items import ScraptDangdang03Item
    
    
    class DangSpider(scrapy.Spider):
        name = 'dang'
        allowed_domains = ['category.dangdang.com']
        start_urls = ['http://category.dangdang.com/cp01.01.02.00.00.00.html']
    
        def parse(self, response):
            # src = '//ul[@id="component_59"]/li//img/@src'
            # name='//ul[@id="component_59"]/li//img/@src'
            # price='//ul[@id="component_59"]/li//p[3]/span[1]/text()'
            # 所有的selector对象都可以再次调用xpath方法
    
            li_list = response.xpath('//ul[@id="component_59"]/li')
            for li in li_list:
                # 第一张图片个其他图片的标签的属性是不一样的
                # 第一张图片是src,其他的dadta-original
                src = li.xpath('.//img/@data-original').extract_first()
                if not src:
                    src = li.xpath('.//img/@src').extract_first()
    
                name = li.xpath('.//img/@alt').extract_first()
                price = li.xpath('.//p[3]/span[1]/text()').extract_first()
                print(src, name, price)
    
                item = ScraptDangdang03Item(src=src, name=name, price=price)
                # 获取一个就交给管道
                yield item
    
    

    1.2item文件的编写

    import scrapy
    
    class ScraptDangdang03Item(scrapy.Item):
        # 通俗将就是要下载的数据都有什么
    
        # 图片
        src = scrapy.Field()
        # 名字
        name = scrapy.Field()
        # 价格
        price = scrapy.Field()
    
    
    

    1.3管道类的编写

    # 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
    
    
    # 要想使用管道必须在settings中开启
    class ScraptDangdang03Pipeline:
        fp = None
    
        def open_spider(self, spider):
            # 在爬虫开始之前就执行的一个方法
            print('开始爬虫'.center(20, '-'))
            self.fp = open('book.json', 'w', encoding='utf-8')
    
        # item就是yield后的item对象
        def process_item(self, item, spider):
            # # 每传递一个对象,就打开一个文件,对文件的操作太过频繁
            # with open('book.json', 'a', encoding='utf-8') as f:
            #     # write必须是一个字符串
            #     # 'w'会覆盖之前的文件内容
            #     f.write(str(item))
            self.fp.write(str(item))
            return item
    
        def close_spider(self, spier):
            print('结束爬虫'.center(20, '-'))
            self.fp.close()
    
    
    import urllib.request
    
    
    # 多条管道开启
    # 定义管道类
    
    class DangDangDownloadPipeline:
        def process_item(self, item, spier):
            url = 'http:' + item.get('src')
            filename = './books' + item.get('name') + '.jpg'
            print('正在下载{}'.format(item.get("name")).center(20,'-'))
            urllib.request.urlretrieve(url=url, filename=filename)
            return item
    
    

    案例2

    2.1 爬虫文件的编写

    import scrapy
    from douban.items import DoubanItem
    
    class DoubanSpider(scrapy.Spider):
        name = 'douban.'
        #allowed_domains = ['www.xxx.con']
        start_urls = ['https://www.qidian.com/all/']
    
        def parse(self, response):
            # 解析小说的作者和标题
            list_data=response.xpath('//div[@class="book-img-text"]/ul/li')
         
            for i in list_data:
                
                title =i.xpath('./div[2]/h2/a/text()').extract_first()
                author=i.xpath('./div[2]/p/a/text()')[0].extract()
            
                item = DoubanItem()
                item['title'] = title
                item['author']=author
    
                yield item #将ltem提交给管道
    
    

    2.2 items文件的编写

    import scrapy
    class DoubanItem(scrapy.Item):
        # define the fields for your item here like:
        title = scrapy.Field()
        author=scrapy.Field()
    

    2.3管道类的重写

    from itemadapter import ItemAdapter
    import pymysql
    
    
    class DoubanPipeline:
        fp = None
    
        # 重写父类方法,该方法只在开始爬虫的时候只调用一次
        def open_spider(self, spider):
            print('开始爬虫')
            self.fp = open('./qidian.txt', 'w', encoding='utf-8')
    
        # 专门处理Item类型对象
        # 该方法可以接收爬虫文件提交过来的item对象
        # 该方法每接收一次Item就会被调用一次
        def process_item(self, item, spider):
            author = item['author']
            title = item['title']
            self.fp.write(title + ':' + author + '\n')
            return item#就会传递给下一个即将执行的管道类
    
        def close_spider(self, spider):
            print('结束爬虫')
            self.fp.close()
    
            # 管道文件中一个管道对应将一组数据存储到一个平台或者载体中
    
    
    class mysqlPipeline:
        conn = None
        cursor = None
    
        def open_spider(self, spider):
            self.conn = pymysql.Connect(host='服务器IP地址', port=端口号, user='root', password=自己的密码', db='qidian',
                                        charset='utf8')
    
        def process_item(self, item, spider):
            self.cursor = self.conn.cursor()
    
            try:
                self.cursor.execute('insert into qidian values(0,"%s","%s")'%(item["title"],item["author"]))
                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()
    
            # 爬虫文件提交的item类型的对象最终会提交给哪一个管道类哪
    
    1.2基于Spider的全站数据解析爬取
    • 就是将网站中某板块下的全部页码对应的页面数据进行爬取
    • 自行手动进行请求发送数据(推荐)
      • 手动发送请求
        • yield scrapy.Resquest(url,callback) callback专门用作数据解析

    案例1.

    import scrapy
    class QidianSpider(scrapy.Spider):
        name = 'qidian'
        #allowed_domains = ['www.xxx.com']
        start_urls = ['https://www.qidian.com/all/']
        #设置一个通用的url
        url='https://www.qidian.com/all/page%d/'
        page_num=2
    
        def parse(self, response):
            list_data=response.xpath('//div[@id="book-img-text"]/ul/li')
            for i in list_data:
                title =i.xpath('./div[2]/h2/a/text()').extract_first()
                author=i.xpath('./div[2]/p/a[1]/text()')[0].extract()
                styple=i.xpath('./div[2]/p/a[2]/text()').extract_first()
                styple1=i.xpath('./div[2]/p/a[3]/text()').extract_first()
                type=styple+'.'+styple1
                #print(title,author,type)
    
            if self.page_num<=3:
                new_url=format(self.url%self.page_num)
                self.page_num+=1
                #手动的发送请求callback回调函数是专门用于数据解析的
                yield scrapy.Request(url=new_url,callback=self.parse)
    

    案例2:当当网青春文学书籍图片下载

    import scrapy
    
    from scrapt_dangdang_03.items import ScraptDangdang03Item
    
    class DangSpider(scrapy.Spider):
        name = 'dang'
        allowed_domains = ['category.dangdang.com']
        start_urls = ['http://category.dangdang.com/cp01.01.02.00.00.00.html']
    
        base_url = 'http://category.dangdang.com/pg'
        page = 1
    
        def parse(self, response):
            # src = '//ul[@id="component_59"]/li//img/@src'
            # name='//ul[@id="component_59"]/li//img/@src'
            # price='//ul[@id="component_59"]/li//p[3]/span[1]/text()'
            # 所有的selector对象都可以再次调用xpath方法
    
            li_list = response.xpath('//ul[@id="component_59"]/li')
            for li in li_list:
                # 第一张图片个其他图片的标签的属性是不一样的
                # 第一张图片是src,其他的dadta-original
                src = li.xpath('.//img/@data-original').extract_first()
                if not src:
                    src = li.xpath('.//img/@src').extract_first()
    
                name = li.xpath('.//img/@alt').extract_first()
                price = li.xpath('.//p[3]/span[1]/text()').extract_first()
                print(src, name, price)
    
                item = ScraptDangdang03Item(src=src, name=name, price=price)
                # 获取一个就交给管道
                yield item
    
            if self.page < 100:
                self.page += 1
                url = self.base_url + str(self.page) + '-cp01.01.02.00.00.00.html'
                # url就是请求地址
                # callback是要执行的 函数,不需要加括号
                yield scrapy.Request(url=url, callback=self.parse)
    
    1.3请求传参
    • 使用场景:如果爬取解析的数据不在同一张页面中(深度爬取),
      将item 传递给请求所对应的回调函数
      需要将不同解析方法中获取的解析数据封装存储到同一个Item中,并且提交到管道

    案例:起点小说网

    1. 爬虫文件的编写
    import scrapy
    from qidian2.items import Qidian2Item
    
    class QidianSpider(scrapy.Spider):
        name = 'qidian'
        #allowed_domains = ['www.xxx.com']
        start_urls = ['https://www.qidian.com/all/']
    
        #解析详情页的信息
        def detail_parse(self,response):
            item=response.meta['item']
            detail_data=response.xpath('//div[@class="book-intro"]/p//text()').extract()
            detail_data=''.join(detail_data)
            item['detail_data']=detail_data
    
            yield item
            # print(detail_data)
        #解析小说名称和详情页的链接地址
    
        def parse(self, response):
    
            list_data=response.xpath('//div[@class="book-img-text"]/ul/li')
            for i in list_data:
                item = Qidian2Item()
                title=i.xpath('./div[2]/h2/a/text()').extract_first()
                item['title']=title
                detail_url='https:'+i.xpath('./div[2]/h2/a/@href').extract_first()
                #发起手动请求
                yield scrapy.Request(url=detail_url,callback=self.detail_parse,meta={'item':item})#请求传参,meta={},可以将meta字典传递给请求所对应的回调函数
    
    1. items文件
    # Define here the models for your scraped items
    
    # See documentation in:
    # https://docs.scrapy.org/en/latest/topics/items.html
    import scrapy
    
    class Qidian2Item(scrapy.Item):
        # define the fields for your item here like:
        title= scrapy.Field()
        detail_data=scrapy.Field()
        pass
    
    1. pipelines文件中管道类的重写
    # 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 Qidian2Pipeline:
        fp=None
        def open_spider(self,spider):
            print('开始爬虫!!!!!!!!!!!!!')
            self.fp=open('./qidian.txt','w',encoding='utf-8')
    
        def process_item(self, item, spider):
            title=item['title']
            detail_data=item['detail_data']
            self.fp.write(title+":"+detail_data+"\n")
            return item
    
        def close_spider(self,spider):
            print('结束爬虫!!!!!!!!!!!!')
            self.fp.close()
    

    图片管道

    图片数据爬取之自定义ImagePipeline

    三个核心的方法:

    • get_media_requests(elf,item,info)根据item中图片连接的属性,返回图片下载的request
    • file_path(self,request,response,info)根据请求和响应返回图片保存的位置(相对于IMAGES STORE)
    • item_completed(self,results,.item,info)图片下载完成后,从results的结果获取图片的保存路径,并设置到item中,最后返回这个item

    案例一

    * 基于scrapy爬取字符串类型数据和爬取图片类型数据区别:
        - 字符串: 只需要基于xpath 进行解析,且提交管道进行持久化存储
        - 图片: 只可以通过xpath解析出图片的src属性,单独的对图片地址发请求获取图片二进制类型的数据。
    * 基于ImagePipeline:
        - 只需要将img的src属性值进行解析,提交到管道,管道就会对图片src进行发送请求获取图片二进制类型的数据,且还会帮我么进行持久化存储。
        
    * 需求:爬取站长素材中高清图片
        - 使用流程:
            1. 数据解析(图片地址)
            2. 将存储图片地址的item提交到指定的管道类中
            3. 在管道文件中自定义一个基于ImagesPipeline的一个管道类
                - get_media_requests()
                - file_path()
                - item_completed()
            4. 在配置文件中:
                - 指定存储图片的目录:IMAGES_STORE='./imgs_站长素材'
                - 开启自定义的管道:
    

    1. 爬虫文件编写解析数据

    #站长素材 高清图片的解析
    import scrapy
    from imgPro.items import ImgproItem
    
    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('//div[@id="container"]/div')
            for i in div_list:
                img_address='https:'+i.xpath('./div/a/img/@src').extract_first()
                #print(img_address)
                #实例化item对象
                item=ImgproItem()
                #传入img_address这个属性,
                item['src']=img_address
                #提交item 到管道
                yield item 
    
    

    2.把图片地址提交到管道

    #来到item 中封装一个属性
    scr=scrapy.Field()
    

    3.将解析到的数据封装到item中

    from imgPro.items import ImgproItem
    #实例化item对象
    item=ImgproItem()
    #传入img_address这个属性,
    item['img_address']=img_address
    #提交item 到管道
    yield item 
    

    4.管道类的重写 pipelines

    import scrapy
    from scrapy.pipelines.images import ImagesPipeline
    
    class ImagsPipeline(ImagesPipeline):
        #可以根据图片地址进行图片数据的请求
        #对item中的图片进行请求操作
        def get_media_requests(self,item ,info ):
            yield scrapy.Request(item['src'])
    
        #指定图片持久化存储的路径
        def file_path(self,request,response=None,info=None):
            img_name=request.url.split('/')[-1]
            return img_name
    
        def item_completed(self,results,item ,info):
                return item #该返回值会传递一给下一个即将被执行的管道类
    
    

    5.settings中图片管道相关参数

    #指定图片存储目录
    IMAGES_STORE='./imgs_站长素材'
    #缩略图
    IMAGES_THUMBS={
        'small':(60,32),
        'big':(120,80)
    }
    

    6.settings配置

    #开启管道, 管道类名换成自定义的名称
    ITEM_PIPELINES = {
        'imgPro.pipelines.ImagsPipeline': 300,
    }
    USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'
    
    # Obey robots.txt rules
    ROBOTSTXT_OBEY = False
    
    #显示报错日志 只显示错误类型的日志信息
    LOG_LEVEL="ERROR"
    ```xxxxxxxxxx11 1#开启管道, 管道类名换成自定义的名称2ITEM_PIPELINES = {3    'imgPro.pipelines.ImagsPipeline': 300,4}5USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'67# Obey robots.txt rules8ROBOTSTXT_OBEY = False910#显示报错日志 只显示错误类型的日志信息11LOG_LEVEL="ERROR"python
    

    案例二

    • 爬虫文件
    import scrapy
    from scrapy.linkextractors import LinkExtractor
    from scrapy.spiders import CrawlSpider, Rule
    
    
    class BookSpider(CrawlSpider):
        name = 'book'
        allowed_domains = ['dushu.com']
        start_urls = ['https://dushu.com/book/']
    
        rules = (
            #所有图书分类css匹配
            Rule(LinkExtractor(restrict_css='.sub-catalog'),  follow=True),
            #分页正则规则
            Rule(LinkExtractor(allow='/book/\d+?_\d+?\.html'),  follow=True),
            #图书正则详情
            Rule(LinkExtractor(allow='/book/\d+/'), callback='parse_item', follow=False),
        )
    
        def parse_item(self, response):
            item = {}
            item['name']=response.css('h1::text').get()
    
            #使用ImagePipiline下载图片时,需要使用image_urls字段,是可迭代的list或tuple类型
            item['cover']=response.css('.pic img::attr("src")').get()
            # item['images']=[]#下载图片后,保存在本地的文件位置
    
            item['price']=response.css('.css::text').get()
            table =response.css('#ctl00_c1_bookleft table')
            item['author']=table.xpath('.//tr[1]/td[2]/text()').get()
            item['publish']=table.xpath('.//tr[2]/td[2]/a/text()').get()
    
            table=response.css('.book-details>table')
            item['isbn']=table.xpath('.//tr[1]/td[2]/text()').get()
            item['publish_date']=table.xpath('.//tr[1]/td[4]/text()').get()
            item['pages']=table.xpath('.//tr[2]/td[4]/text()').get()
    
            yield item
    
    
    • pipeline
    # 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
    from scrapy.pipelines.images import  ImagesPipeline
    from scrapy import  Request
    class DushuPipeline:
        def process_item(self, item, spider):
            return item
    class BookImagePipiline(ImagesPipeline):
        DEFAULT_IMAGES_URLS_FIELD = 'cover'
        DEFAULT_IMAGES_RESULT_FIELD = 'path'
        def get_media_requests(self, item, info):
            return Request(item.get('cover'),meta={'book_name':item['name']})
    
    
        def file_path(self, request, response=None, info=None, *, item=None):
            name=request.meta['book_name']
            return f'{name}.jpg'
        def item_completed(self, results, item, info):
            item['path']=[ v['path'] for k,v in results if k]
            return item
    
    • settings
    import os.path
    
    BOT_NAME = 'dushu'
    
    SPIDER_MODULES = ['dushu.spiders']
    NEWSPIDER_MODULE = 'dushu.spiders'
    
    USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
    
    ROBOTSTXT_OBEY = False
    
    DEFAULT_REQUEST_HEADERS = {
      'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
      'Accept-Language': 'en',
      'Referer':'https://www.dushu.com/book/1114_13.html'
    }
    
    
    ITEM_PIPELINES = {
       'dushu.pipelines.DushuPipeline': 300,
        'dushu.pipelines.BookImagePipiline':302
    }
    
    #配置图片管道的相关参数
    BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    IMAGES_STORE=os.path.join(BASE_DIR,'images')
    IMAGES_THUMBS={
        'small':(60,32),
        'big':(120,80)
    }
    

    CrawlSpider

    1.继承自scrapy.Spider
    2.独门秘笈
    	CrawlSpider可以定义规则,再解析html内容的时候,可以根据链接规则提取出指定的链接,然后再向这些链接发送请求
        所以,如果有需要跟进链接的需求,意思就是爬取了网页之后,需要提取链接再次爬取,使用CrawlSpider是非常合适的
    
    3.提取链接
     	链接提取器,在这里就可以写规则提取指定链接
    scrapy.linkextractors.LinkExtractor(
    	a11ow=(),	#正则表达式提取符合正则的链接
    	deny =()	#(不用)正则表达式不提取符合正则的链接
    	allow_domains =()	#(不用)允许的域名
    	deny_domains =()	#(不用)不允许的域名
    	restrict xpaths=(),	#xpath,提取符合xpathi规则的链接
    	restrict_css =()	#提取符合选择器规则的链接)
    4.模拟使用
    	正则用法:1inks1 = LinkExtractor(allow=r'1ist23_\d+\.html')
    	xpath:	links2 = LinkExtractor(restrict_xpaths=r'//div[@class="x"]')
    	css用法:links3 = LinkExtractor(restrict_css='.X')
    5.提取连接
        link.extract_links(response)
    
    6.注意事项
        【注1】callback只能写函数名字符串,cal1back='parse_item'
        【注2】在基本的spider中,如果重新发送清求,那里的cal1back写的是    cal1back=self.parse_item
        【注3】follow=true是否跟进就是按照提取连接规则进行提取
    

    CrawlSpider案例

    案例一:读书网数据入库

    l.创建项目:scrapy startproject dushuproject
    2.跳转到spiders路径 cd\dushuproject\dushuproject\spiders
    3.创建爬虫类:scrapy genspider -t crawl read www.dushu.com
    4.items
    5.spiders
    6.settings
    7.pipelines
        数据保存到本地
        数据保存到mysq1数据库
    
    

    数据入库:

    (1)settings配置参数:
        DBH0T='127.0.0.1'
        DB_PORT =3306
        DB USER 'root'
        DB PASSWORD '1234'
        DB_NAME ='readbood'
        DB_CHARSET = 'utf8'
    (2)管道配置
        #加载配置文件
        from scrapy.utils.project import get_project_settings
        import pymysql
        class MysqlPipeline(object):
    	#__init__方法和open_spider的作用是一样的
    	#init获取settings中的连接参数
    

    代码实现:

    • 爬虫文件
    import scrapy
    from scrapy.linkextractors import LinkExtractor
    from scrapy.spiders import CrawlSpider, Rule
    
    from scrapy_readbook_05.items import ScrapyReadbook05Item
    
    
    class ReadSpider(CrawlSpider):
        name = 'read'
        allowed_domains = ['www.dushu.com']
        start_urls = ['https://www.dushu.com/book/1188_1.html']
    
        rules = (
            Rule(LinkExtractor(allow=r'/book/1188_\d+\.html'), callback='parse_item', follow=True),
        )
    
        def parse_item(self, response):
            img_list=response.xpath('//div[@class="bookslist"]/ul/li//img')
            for img in img_list:
                src=img.xpath('./@data-original').extract_first()
                name=img.xpath('./@alt').extract_first()
                book=ScrapyReadbook05Item(name=name,src=src)
                yield  book
    
    
    • item文件
    import scrapy
    
    
    class ScrapyReadbook05Item(scrapy.Item):
        # define the fields for your item here like:
        # name = scrapy.Field()
        name=scrapy.Field()
        src=scrapy.Field()
    
    
    • settings.py
    DB_HOST = '127.0.0.1'
    DB_PORT = 3306
    DB_USER = 'root'
    DB_PASSWORD = '123.com'
    DB_NAME = 'readbook'
    DB_CHARSET = 'utf8'
    
    # Configure item pipelines
    # See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
    ITEM_PIPELINES = {
        'scrapy_readbook_05.pipelines.ScrapyReadbook05Pipeline': 300,
        'scrapy_readbook_05.pipelines.MysqlPipiline': 301,
    }
    
    
    • 管道实现本地存储和数据库存储
    # 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 ScrapyReadbook05Pipeline:
        def open_spider(self, spider):
            self.fp = open('./book.json', 'w', encoding='utf-8')
    
        def process_item(self, item, spider):
            print('正在写入数据 :  ' + item.get('name'))
            self.fp.write(str(item))
            return item
    
        def close_spider(self, spider):
            self.fp.close()
    
    
    # 加载settings文件
    from scrapy.utils.project import get_project_settings
    import pymysql
    
    
    class MysqlPipiline:
        def open_spider(self, spider):
            settings = get_project_settings()
            self.host = settings['DB_HOST']
            self.port = settings['DB_PORT']
            self.user = settings['DB_USER']
            self.password = settings['DB_PASSWORD']
            self.name = settings['DB_NAME']
            self.charset = settings['DB_CHARSET']
            self.connect()
    
        def connect(self):
            self.conn = pymysql.connect(
                user=self.user,
                host=self.host,
                port=self.port,
                password=self.password,
                database=self.name,
                charset=self.charset
            )
            self.cursor = self.conn.cursor()
    
        def process_item(self, item, spider):
            sql = 'insert into book(name,src) values("{}","{}")'.format(item['name'], item['src'])
            # 执行sql语句
            self.cursor.execute(sql)
            # 提交
            self.conn.commit()
            print(f'数据 : {item["name"]}提交成功......')
            return item
    
        def close_spider(self, spider):
            self.cursor.close()
            self.conn.close()
    

    案例二: 使用默认的ImagesPipeline下载图片

    • 爬虫文件
    import scrapy
    from scrapy.linkextractors import LinkExtractor
    from scrapy.spiders import CrawlSpider, Rule
    
    
    class BookSpider(CrawlSpider):
        name = 'book'
        allowed_domains = ['dushu.com']
        start_urls = ['https://dushu.com/book/']
    
        rules = (
            #所有图书分类css匹配
            Rule(LinkExtractor(restrict_css='.sub-catalog'),  follow=True),
            #分页正则规则
            Rule(LinkExtractor(allow='/book/\d+?_\d+?\.html'),  follow=True),
            #图书正则详情
            Rule(LinkExtractor(allow='/book/\d+/'), callback='parse_item', follow=False),
        )
    
        def parse_item(self, response):
            item = {}
            item['name']=response.css('h1::text').get()
    
            #使用默认的ImagePipiline下载图片时,需要使用image_urls字段,是可迭代的list或tuple类型
            item['image_urls']=response.css('.pic img::attr("src")').extract()
            item['images']=[]#下载图片后,保存在本地的文件位置
    
            item['price']=response.css('.css::text').get()
            table =response.css('#ctl00_c1_bookleft table')
            item['author']=table.xpath('.//tr[1]/td[2]/text()').get()
            item['publish']=table.xpath('.//tr[2]/td[2]/a/text()').get()
    
            table=response.css('.book-details>table')
            item['isbn']=table.xpath('.//tr[1]/td[2]/text()').get()
            item['publish_date']=table.xpath('.//tr[1]/td[4]/text()').get()
            item['pages']=table.xpath('.//tr[2]/td[4]/text()').get()
    
            yield item
    
    • settings.py
    USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'
    
    ROBOTSTXT_OBEY = False
    
    ITEM_PIPELINES = {
       'dushu.pipelines.DushuPipeline': 300,
        'scrapy.pipelines.images.ImagesPipeline':301
    }
    
    #配置图片管道的相关参数
    BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    IMAGES_STORE=os.path.join(BASE_DIR,'images')
    
    
    DEFAULT_REQUEST_HEADERS = {
      'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
      'Accept-Language': 'en',
      'Referer':'https://www.dushu.com/book/1114_13.html'
    }
    
    

    日志信息和等级

    (1)日志级别:
        CRITICAL:	严重错误
        ERROR: 		一股错误
        WARNING:	警告
        INFO:		一般信息
        DEBUG:		调试信息
        
        默认的日志等级是DEBUG
        只要出现了DEBUG或者DEBUG以上等级的日志
        那么这些日志将会打印
        
    (2)settings.py文件设置:
        默认的级别为DEUG,会显示上面所有的信息
        在配置文件中settings.py
        L0G_FILE:将屏幕显示的信息全部记录到文件中,屏幕不再显示,注意文件后缀一定是.10g
        L0G_LEVEL:设置日志显示的等级,就是显示哪些,不显示哪些
        
     #settigs.py中指定日志
    L0G_FILE='logdemo.log'#将屏幕显示的信息全部记录到文件中,屏幕不再显示,注意文件后缀一定是.10g
    L0G_LEVEL='INFO'#设置日志显示的等级,就是显示哪些,不显示哪些
    

    scrapy的post请求

    (1)重写start_requests方法:
    	def start_requests(self)
    (2)start_requests的返回值:
    	scrapy.FormRequest(url=url,headers=headers,callback=self.parse_item,formdata=data)
        url:要发送的post地址
        headers:可以定制头信息
        ca11back:回调函数
        formdata:post所携带的数据,这是一个字典
    
    import scrapy
    import json
    
    
    class TestpostSpider(scrapy.Spider):
        name = 'testpost'
        allowed_domains = ['fanyi.baidu.com']
        #post请求如果没有参数,那么这个请求就没有用了
        # post方法 start_urls没有用了
        # parse方法也没有用了
        # start_urls = ['https://fanyi.baidu.com/sug']
        #
        # def parse(self, response):
        #     pass
    
        def start_requests(self):
            url = 'https://fanyi.baidu.com/sug'
            data = {
                'kw': 'python'
            }
            yield scrapy.FormRequest(url=url, formdata=data, callback=self.parse_second)
    
        def parse_second(self, response):
            content = response.text
            obj = json.loads(content)
            print(obj)
    

    分布式爬虫

    pip install scrapy-redis
    

    安装服务器并执行

    docker pull redis
    docker run -dit --name redis-server -p 6378:6379 redis
    
    #然后使用 docker ps 查看运行的容器 
    

    image-20230210170248196

    创建爬虫项目

    scrapy startproject dushu_redis
    cd dushu_redis\dushu_redsi\spiders
    scrapy genspider guoxu dushu.com
    

    编写爬虫程序

    #继承RedisSpider
    from scrapy_redis.spiders import RedisSpider
    from scrapy import Request
    
    
    class GuoxueSpider(RedisSpider):
        name = 'guoxue'
        allowed_domains = ['dushu.com']
        redis_key = 'gx_start_urls'
    
        def parse(self, response):
            for url in response.css('.sub-catalog a::attr("href")').extract():
                yield Request(url='https://www.dushu.com' + url, callback=self.parse_item)
    
        def parse_item(self, response):
            div_list = response.css('.book-info')
            for i in div_list:
                item = {}
                item['name'] = i.xpath('./div//img/@alt').get()
                item['cover'] = i.xpath('./div//img/@src').get()
                item['detail_url'] = i.xpath('./div/a/@href').get()
                yield item
            # 下一页
            next_url=response.css('.pages ').xpath('./a[last()]/@href').get()
    
            yield Request(url='https://www.dushu.com' + next_url, callback=self.parse_item)
    
    
    

    settings配置

    #配置调度器
    SCHEDULER = 'scrapy_redis.scheduler.Scheduler'
    SCHEDULER_PERSIST = True
    # 配置去重
    DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'
    
    #配置redis消息队列服务器
    REDIS_URL='redis://192.168.163.128:6378/0'
    
    USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36'
    
    ROBOTSTXT_OBEY = False
    

    配置好后,然后执行爬虫文件

    scrapy crawl guoxue 
    

    进入到redis数据库

     docker exec -it redis-server bash
    
    root@1472cc4b0e69:/data# redis-cli
    127.0.0.1:6379> select 0
    OK
    127.0.0.1:6379> keys *
    (empty array)
    127.0.0.1:6379> lpush gx_start_urls  https://www.dushu.com/guoxue/
    (integer) 1
    127.0.0.1:6379> 
    

    你可能感兴趣的:(Python,爬虫,beautifulsoup,python,学习)