百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

2021年最全的Python爬虫学习笔记(下)

csdh11 2024-11-30 14:14 4 浏览

3. 模拟登录cookie操作

  • 需求:爬取当前用户的相关用户信息(个人主页中显示的用户信息)
  • http/https协议:无状态。没有请求到对应页面数据的原因:发起的第二次基于个人主页页面请求的时候,服务器并不知道该次请求是基于登录状态下的请求。
  • cookie:用来让服务器端记录客户端的相关状态手动处理:抓包工具获取 Cookie 值,将值封装到 headers 中(不推荐)自动处理:Cookie 值的来源是哪里?模拟登录 post 请求后,由服务器端创建的。

session会话对象:

私信小编01即可获取大量python学习资源

1. 可以进行请求的发送;

2. 如果请求过程中产生了Cookie,则该Cookie会被自动存储/携带在该session对象中。创建一个session对象:session = requests.Session( )

使用session对象进行模拟登录post请求的发送(Cookie会被存储在session中)session对象对个人主页对应的get请求进行发送(携带了Cookie)

#####基于前一节代码之上####
session = requests.Session()

#爬取当前用户的相关用户信息
'''手动获取Cookie(不推荐) headers = {
   ‘'Cookie':'xxxx'
    }'''
detail_url = 'http://www.renren.com/976279344/profile'
detail_page_test = session.get(url = detail_url,headers = headers).text
with open('bobo.html','w',encoding = 'utf-8' ) as fp:
    fp.write(detail_page_test)

4. 代{过}{滤}理理论讲解

  • 代{过}{滤}理:破解封 IP 这种反爬机制。
  • 什么是代{过}{滤}理?代{过}{滤}理服务器。
  • 代{过}{滤}理的作用:突破自身 IP 被访问的限制可以隐藏自身真实的 IP,免受攻击
  • 相关网站:快代{过}{滤}理西祠代{过}{滤}理
  • 代{过}{滤}理 ip 的类型:http:只能应用到 http 协议对应的 url 中https:只能应用到 https 协议对应的 url 中
  • 代{过}{滤}理ip的匿名度:透明:服务器知道该次请求使用了代{过}{滤}理,也知道请求对应的真实 ip匿名:知道使用了代{过}{滤}理,不知道真实 ip高匿:不知道使用了代{过}{滤}理,也不知道真实 ip

5. 代{过}{滤}理在爬虫中的应用

import requests

url = 'http://www.baidu.com/s?wd=ip'
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36'
    }
page_text = requests.get(url = url, headers = headers, proxies = {"http": "http://124.205.155.153:9090"}).text
with open('ip.html', 'w', encoding = 'utf-8') as fp:
    fp.write(page_text)

六、高性能异步爬虫

1. 异步爬虫概述

  • 同步:不同程序单元为了完成某个任务,在执行过程中需靠某种通信方式以协调一致,称这些程序单元是同步执行的。 例如购物系统中更新商品库存,需要用 “行锁” 作为通信信号,让不同的更新请求强制排队顺序执行,那更新库存的操作是同步的。 简言之,同步意味着有序。
  • 异步:为完成某个任务,不同程序单元之间过程中无需通信协调,也能完成任务的方式,不相关的程序单元之间可以是异步的。 例如,爬虫下载网页。调度程序调用下载程序后,即可调度其他任务,而无需与该下载任务保持通信以协调行为。不同网页的下载、保存等操作都是无关的,也无需相互通知协调。这些异步操作的完成时刻并不确定。 简言之,异步意味着无序。
  • 目的:在爬虫中使用异步实现高性能的数据爬取操作。
import requests

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36'
}
urls = [
    'https://downsc.chinaz.net/Files/DownLoad/jianli/202102/jianli14667.rar',
    'https://downsc.chinaz.net/Files/DownLoad/jianli/202102/jianli14665.rar',
    'https://downsc.chinaz.net/Files/DownLoad/jianli/202102/jianli14648.rar'
]

def get_content(url):
    print('正在爬取:', url)
    # get方法是一个阻塞的方法
    response = requests.get(url=url, headers=headers)
    if response.status_code == 200:
        return response.content

def parse_content(content):
    print('响应数据的长度为:', len(content))

for url in urls:
    content = get_content(url)
    parse_content(content)

2. 多线程and多线程

异步爬虫的方式:

  • 多线程,多进程:(不建议)好处:可以为相关阻塞的操作单独开启线程或者进程,阻塞操作就可以异步执行弊端:无法无限制的开启多线程或者多进程

3. 线程池and进程池

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

4. 线程池的基本使用

import time
#使用单线程串行方式执行
def get_page(str):
    print('正在下载:',str)
    time.sleep(2)
    print('下载成功:',str)

name_list = ['xiaozi','aa','bb','cc']
start_time = time.time()
for i in range(len(name_list)):
    get_page(name_list[i])
end_time = time.time()
print('%d second' % (end_time-start_time))
#导入线程池模块对应的类
import time
from multiprocessing.dummy import Pool

#使用线程池方式执行
start_time = time.time()
def get_page(str):
    print('正在下载:', str)
    time.sleep(2)
    print('下载成功:', str)

name_list = ['xiaozi','aa','bb','cc']

#实例化一个线程池对象
pool = Pool(4)      #线程池开辟4个线程
#将列表中每一个列表元素传递给get_page进行处理
pool.map(get_page, name_list)

end_time = time.time()
print(end_time - start_time)

5. 线程池案例应用

# 需求:爬取梨视频视频数据
import requests
import os
from multiprocessing.dummy import Pool
from lxml import etree
import random

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36'
}
# 原则:线程池处理的是阻塞且耗时的操作

if __name__ == '__main__':
    # 生成一个存放视频的文件夹
    if not os.path.exists('./video'):
        os.mkdir('./video')
        # 对下述url发起请求解析出视频详情页的url和视频的名称
    url = 'https://www.pearvideo.com/category_5'
    page_text = requests.get(url=url, headers=headers).text

    tree = etree.HTML(page_text)
    li_list = tree.xpath('//ul[@id="listvideoListUl"]/li')
urls = []  # 存储所有视频的链接和文字
for li in li_list:
    detail_url = 'https://www.pearvideo.com/' + li.xpath('./div/a/@href')[0]
    name = li.xpath('./div/a/div[2]/text()')[0] + '.mp4'
    # print(detail_url,name)

    # 对详情页的url发起请求
    detail_page_text = requests.get(url=detail_url, headers=headers).text
    # 从详情页中解析出视频的地址
    #### 视频的方法在2021/02/27 不可使用,梨视频又更改了页面源码,mp4是动态加载出来的,mp4文件经ajax请求得到,需要抓包ajax
    #### 参考 https://www.cnblogs.com/qianhu/p/14027192.html的操作
    detail_tree = etree.HTML(detail_page_text)
    name = detail_tree.xpath('//*[@id="detailsbd"]/div[1]/div[2]/div/div[1]/h1/text()')[0]
    str_ = str(li.xpath('./div/a/@href')[0]).split('_')[1]
    ajax_url = 'https://www.pearvideo.com/videoStatus.jsp?'
    params = {
        'contId': str_,
        'mrd': str(random.random())
    }
    ajax_headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36',
        'Referer': 'https://www.pearvideo.com/video_' + str_
    }
    dic_obj = requests.get(url=ajax_url, params=params, headers=ajax_headers).json()
    video_url = dic_obj["videoInfo"]['videos']["srcUrl"]

    video_true_url = ''
    s_list = str(video_url).split('/')
    for i in range(0, len(s_list)):
        if i < len(s_list) - 1:
            video_true_url += s_list[i] + '/'
        else:
            ss_list = s_list[i].split('-')
            for j in range(0, len(ss_list)):
                if j == 0:
                    video_true_url += 'cont-' + str_ + '-'
                elif j == len(ss_list) - 1:
                    video_true_url += ss_list[j]
                else:
                    video_true_url += ss_list[j] + '-'
    dic = {
        'name': name,
        'url': video_true_url
    }
    urls.append(dic)

def get_video_data(dic):
    urll = dic['url']
    data = requests.get(url=urll, headers=headers).content
    path = './video/' + dic['name'] + '.mp4'
    print(dic['name'], '正在下载.......')
    # 持久化存储操作
    with open(path, 'wb') as fp:
        fp.write(data)
        print(dic['name']+ '.mp4', '下载成功!')

# 使用线程池对视频数据进行请求(较为耗时的阻塞操作)
pool = Pool(4)
pool.map(get_video_data, urls)

pool.close()
pool.join()

6. 协程相关概念回顾

  • 协程:英文叫做 Coroutine,又称微线程,纤程,协程是一种用户态的轻量级线程。 协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此协程能保留上一次调用时的状态,即所有局部状态的一个特定组合,每次过程重入时,就相当于进入上一次调用的状态。 协程本质上是个单进程,协程相对于多进程来说,无需线程上下文切换的开销,无需原子操作锁定及同步的开销,编程模型也非常简单。 我们可以使用协程来实现异步操作,比如在网络爬虫场景下,我们发出一个请求之后,需要等待一定的时间才能得到响应,但其实在这个等待过程中,程序可以干许多其他的事情,等到响应得到之后才切换回来继续处理,这样可以充分利用 CPU 和其他资源,这就是异步协程的优势。
  • 单线程+异步协程:(推荐)event_loop:事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上,当满足某些条件的时候,函数就会被循环执行。coroutine:协程对象,我们可以将协程对象注册到事件循环中,它会被事件循环调用,我们可以使用 async 关键字来定义一个方法,这个方法在调用时不会立即执行,而是返回一个协程对象。task:任务,他是对协程对象的进一步封装,包含了任务的各个状态。future:代表将来执行或还没有执行的任务,实际上和 task 没有本质区别。async:定义一个协程。await:用来挂起阻塞方法的执行。

7. 协程相关操作回顾

import asyncio
async def request(url):
    print('正在请求的url是',url)
    print('请求成功,',url)
    return url
#asyncio修饰的函数,调用之后返回的一个协程对象
c = request('www.baidu.com')

# #创建一个事件循环对象
# loop = asyncio.get_event_loop()
#
# #将协程对象注册到loop中,然后启动loop
# loop.run_until_complete(c)

# #task的使用
# loop = asyncio.get_event_loop()
# #基于loop创建一个task任务对象
# task = loop.create_task(c)
# print(task)
#
# loop.run_until_complete(task)
# print(task)

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

def callback_func(task):
    #result返回的就是任务对象中封装的协程对象对应函数的返回值
    print(task.result())
#绑定回调
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(c)
#将回调函数绑定到任务对象中
task.add_done_callback(callback_func)
loop.run_until_complete(task)

8. 多任务异步协程实现

import time
import asyncio

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

start = time.time()
urls =[
    'www.baidu.com',
    'www.sougou.com',
    'www.goubanjia.com'
]
#任务列表:存放多个任务对象
stasks = []
for url in urls:
    c = request(url)
    task = asyncio.ensure_future(c)
    stasks.append(task)

loop = asyncio.get_event_loop()
#需要将任务列表封装到wait中
loop.run_until_complete(asyncio.wait(stasks))

print(time.time()-start)

9. aiohttp 模块引出

######未能实现异步进程,还是同步操作
import requests
import asyncio
import time

start = time.time()
urls = [
    'http://127.0.0.1:1080/bobo',
    'http://127.0.0.1:1080/jay',
    'http://127.0.0.1:1080/tom'
]

async def get_page(url):
    print('正在下载', url)
    #requests模块发起的请求是基于同步的,不能在异步模块中使用,否则会中断异步操作,必须使用基于异步的网络请求模块进行url的请求发送
    #aiphttp模块引入
    response = requests.get(url = url)
    print('下载完毕', response.text)

tasks = []

for url in urls:
    c = get_page(url)
    task = asyncio.ensure_future(c)
    tasks.append(task)

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

end = time.time()
print('总耗时:', end-start)

10. aiohttp + 多任务异步协程实现异步爬虫

#环境的安装    pip install aiohttp
#使用aiohttp模块中的ClientSession
import asyncio
import time
import aiohttp

start = time.time()
urls = [
    'http://www.baidu.com',
    'http://www.sougou.com',
    'http://www.taobao.com'
]

async def get_page(url):
    async with aiohttp.ClientSession() as session:
        #get()、post():
        #headers,params/data,proxy='http://ip:port'
        async with await session.get(url) as response:
            #text()返回的是字符串形式的响应数据
            #read()返回的是二进制形式的响应数据
            #json()返回的是json对象
            #注意:在获取响应数据操作之前,一定要使用await手动挂起
            page_text = await response.text()
            #print(page_text)

tasks = []

for url in urls:
    c = get_page(url)
    task = asyncio.ensure_future(c)
    tasks.append(task)

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

end = time.time()
print('总耗时:', end-start)

七、动态加载数据处理

1. selenium简介

  • 问题:selenium模块和爬虫之间具有怎样的关联?便捷地获取网站中动态加载的数据
  • 便捷实现模拟登录
  • 什么是selenium模块?基于浏览器自动化的一个模块。

2. selenium初试

selenium使用流程:

  • 环境安装:pip install selenium
  • 下载一个对应浏览器的驱动程序(以谷歌浏览器为例)
    • 实例化一个浏览器对象
    • 编写基于浏览器自动化的操作代码
      • 发起请求:get(url)
      • 标签定位:find系列方法
      • 标签交互:send_keys('xxxxxx')
      • 执行js程序:excute_script('jsCode')
      • 前进、后退:forward( )、back( )
      • 关闭浏览器:quit( )
# selenium操纵浏览器
#### Tip:作者Chrome是88版本,直接下载88的chromedriver成功运行

from selenium import webdriver
from lxml import etree
from time import sleep
# 实例化一个浏览器对象(传入浏览器的驱动程序)
bro = webdriver.Chrome(executable_path='./chromedriver.exe')
# 让浏览器发起一个指定的url对应请求
bro.get('http://scxk.nmpa.gov.cn:81/xk/')     

# 获取浏览器当前页面的页面源码数据
page_text = bro.page_source

# 解析企业名称
tree = etree.HTML(page_text)
li_list = tree.xpath('//ul[@id="gzlist"]/li')
for li in li_list:
    name = li.xpath('./dl/@title')[0]
    print(name)
sleep(5)
bro.quit()

3. selenium其他自动化操作

from selenium import webdriver
from time import sleep
bro = webdriver.Chrome(executable_path='./chromedriver.exe')
bro.get('https://www.taobao.com/')
# 标签定位
search_input = bro.find_element_by_id('q')
# 标签的交互
search_input.send_keys('iphone')
# 执行一组js程序   相当于F12--Console执行js代码
bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
sleep(2)
# 点击搜索按钮
btn = bro.find_element_by_css_selector('.btn-search')
btn.click()

bro.get('https://baidu.com/')
sleep(2)
# 回退
bro.back()
sleep(2)
# 前进
bro.forward()

sleep(5)
bro.quit()

4. iframe 处理+动作链

**selenium处理iframe:**

  • 如果定位的标签存在于iframe标签之中,则必须使用switch_to.frame(id)
  • 动作链(拖动):from selenium.webdriver import ActionChains实例化一个动作链对象:action = ActionChains(bro)click_and_hold(div):长按且点击move_by_offset(x,y)perform( ):让动作链立即执行action.release( ):释放动作链对象
from selenium import webdriver
from time import sleep
# 导入动作链对应的类
from selenium.webdriver import ActionChains

bro = webdriver.Chrome(executable_path='./chromedriver.exe')

bro.get('https://www.runoob.com/try/try.php?filename=jqueryui-example-droppable')

# 如果定位的标签是存在与iframe标签之中的,直接通过find方式会报错,则必须通过另外的操作来进行标签定位
bro.switch_to.frame('iframeResult')     #切换浏览器标签定位的作用域
div = bro.find_element_by_id('draggable')

# 动作链
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(11, 0).perform()
    sleep(0.3)

# 释放动作链
action.release()

bro.quit()

5. selenium模拟登录空间

#模拟登录QQ空间,运行前需要将代码中“QQ号码”和“QQ密码”改写
from selenium import webdriver
from time import sleep

bro = webdriver.Chrome(executable_path='./chromedriver.exe')
bro.get('https://qzone.qq.com/')
bro.switch_to.frame('login_frame')

a_tag = bro.find_element_by_id('switcher_plogin')
a_tag.click()

userName_tag = bro.find_element_by_id('u')
password_tag = bro.find_element_by_id('p')
sleep(1)
userName_tag.send_keys('QQ号码')
password_tag.send_keys('QQ密码')
sleep(1)
btn = bro.find_element_by_id('login_button')
btn.click()

sleep(3)

bro.quit()

6. 无头浏览器+规避操作

from selenium import webdriver
from time import sleep
#实现无可视化界面
from selenium.webdriver.chrome.options import Options
#实现规避检测
from selenium.webdriver import ChromeOptions

#实现无可视化界面的操作
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')

#实现规避检测
option = ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])

#如何实现让selenium规避被检测到的风险
bro = webdriver.Chrome(executable_path='./chromedriver.exe', chrome_options=chrome_options,options=option)

#无可视化界面(无头浏览器) phantomJs
bro.get('https://www.baidu.com')

print(bro.page_source)
sleep(2)
bro.quit()

7. 超级鹰的基本使用

超级鹰:

  • 注册:普通用户
  • 登录:普通用户
  • 题分查询:充值
  • 软件ID——创建一个软件ID
  • 下载示例代码

8. 12306模拟登录

编码流程:

  • 使用selenium打开登录界面
  • 对当前selenium打开的这张界面进行截图
  • 对截取的图片进行局部区域(验证码图片)的裁剪
    • 好处:将验证码图片和模拟登录进行一一对应
  • 使用超级鹰识别验证码图片(坐标)
#!/usr/bin/env python
# coding:utf-8

import requests
from hashlib import md5

########下述为超级鹰示例代码
class Chaojiying_Client(object):

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

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

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

############上述为超级鹰的示例代码

# 使用selenium打开登录页面
from selenium import webdriver
import time
from PIL import Image
from selenium.webdriver import ActionChains

bro = webdriver.Chrome(executable_path='./chromedriver.exe')
bro.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
  "source": """
    Object.defineProperty(navigator, 'webdriver', {
      get: () => undefined
    })
  """
})

# bro.execute_script(script)
bro.get('https://kyfw.12306.cn/otn/resources/login.html')

#最大化浏览器窗口
bro.maximize_window()
time.sleep(1)

# 先点击选择  账号登录
zhanghao_tag = bro.find_element_by_class_name('login-hd-account')
zhanghao_tag.click()
time.sleep(1)

# save_screenshot就是将当前页面进行截图且保存
bro.save_screenshot('aa.png')

#确定验证码图片对应的左上角和右下角的坐标(裁剪的区域就确定)
code_img_ele = bro.find_element_by_class_name('touclick-wrapper')
location = code_img_ele.location  # 验证码图片左上角的坐标 x,y
print('location:', location)
size = code_img_ele.size  #验证码标签对应的长和宽
print('size:', size)

# 左上角和右下角坐标  #此处 *1.25 原因是作者window电脑默认显示布局为125%(电脑设置--显示--缩放与布局),不乘1.25取不到图片正确位置
rangle = (location['x']*1.25, location['y']*1.25, (location['x']+size['width'])*1.25, (location['y']+size['height'])*1.25)
# 至此验证码图片区域就确定下来了

i = Image.open('./aa.png')
code_img_name = './code.png'

# crop根据指定区域进行图片裁剪
frame = i.crop(rangle)
frame.save(code_img_name)
time.sleep(3)

# 将验证码图片提交给超级鹰进行识别

chaojiying = Chaojiying_Client('超级账号', '超级密码', '软件ID')
im = open('code.png', 'rb').read()
print(chaojiying.PostPic(im, 9004)['pic_str'])

result = chaojiying.PostPic(im, 9004)['pic_str']
all_list = []   #要存储即将被点击的点的坐标  [[x1,y1],[x2,y2]]
if '|' in result:
    list_1 = result.split('|')
    count_1 = len(list_1)
    for i in range(count_1):
        xy_list = []
        x = int(list_1[i].split(',')[0])
        y = int(list_1[i].split(',')[1])
        xy_list.append(x)
        xy_list.append(y)
        all_list.append(xy_list)
else:
    x = int(result.split(',')[0])
    y = int(result.split(',')[1])
    xy_list = []
    xy_list.append(x)
    xy_list.append(y)
    all_list.append(xy_list)
print(all_list)
# 遍历列表,使用动作链对每一个列表元素对应的x,y指定的位置进行点击操作
for l in all_list:
    x = l[0]
    y = l[1]
    #这里的/1.25,是因为,电脑设置125%,而网页是100%的,所以,要确定网页中对应位置,除以1.25即可
    ActionChains(bro).move_to_element_with_offset(code_img_ele, x/1.25, y/1.25).click().perform()
    time.sleep(1)

bro.find_element_by_id('J-userName').send_keys('12306账号')
time.sleep(1)
bro.find_element_by_id('J-password').send_keys('12306密码')
time.sleep(1)
bro.find_element_by_id('J-login').click()
time.sleep(5)

# # 滑块操作,12306检测selenium,,,,滑块总是刷新重试,
# action = ActionChains(bro)
# try:
#     slider = bro.find_element_by_css_selector('#nc_1_n1z')
#     action.click_and_hold(slider)
#     action.move_by_offset(300, 0).perform()
#     time.sleep(15)
#     action.release()
# except Exception as e:
#     print(e)

bro.quit()

八、scrapy框架

1. scrapy框架初识

  • 什么是框架?就是一个集成了很多功能并且具有很强通用性的一个项目模板。
  • 如何学习框架?专门学习框架封装的各种功能的详细用法。
  • 什么是scrapy?爬虫中封装好的一个明星框架。功能:高性能的持久化存储,异步的数据下载,高性能的数据解析,分布式

2. scrapy基本使用

scrapy框架的基本使用:

  • 环境的安装:
    • mac or linux:pip install scrapy
    • windows:
      • pip install wheel
      • 下载twisted
      • 安装twisted:pip install Twisted-20.3.0-cp39-cp39-win_amd64.whl
      • pip install pywin32
      • pip install scrapy
      • 测试:在终端里录入scrapy指令,没有报错即表示安装成功!
  • 创建一个工程:scrapy startproject xxxPro
  • cd xxxPro
  • 在spiders子目录中创建一个爬虫文件
    • scrapy genspider spiderName www.xxx.com
  • 执行工程:
    • scrapy crawl spiderName
###firstBlood__first
import scrapy

class FirstSpider(scrapy.Spider):
    #爬虫文件的名称:就是爬虫源文件的一个唯一标识
    name = 'first'
    #允许的域名:用来限定start_urls列表中哪些url可以进行请求发送
    # allowed_domains = ['www.baidu.com']

    #起始的url列表:该列表中存放的url会被scrapy自动进行请求的发送
    start_urls = ['https://www.baidu.com/', 'https://www.sogou.com/']

    #用作于数据解析:response参数表示的就是请求成功后对应的响应对象
    def parse(self, response):
        print(response)

3. scrapy数据解析操作

import scrapy

class QiubaiSpider(scrapy.Spider):
    name = 'qiubai'
    #allowed_domains = ['www.xxx.com']
    start_urls = ['https://www.qiushibaike.com/text/']

    def parse(self, response):
        #解析作者的名称+段子的内容
        div_list = response.xpath('//div[@id="col1 old-style-col1"]/div')
        for div in div_list:
            #xpath返回的是列表,当时列表元素一定是Selector类型的对象
            #extract可以将Selector对象中data参数存储的字符串提取出来
            author = div.xpath('./div[1]/a[2]/h2/text()')[0].extract()
            #列表调用了extract之后。则表示将列表中每一个Selector对象中data对应的字符串提取了出来
            content = div.xpath('./a[1]/div/span//text()').extract()
            content = ''.join(content)

            print(author,content)
            break

4. 基于终端指令的持久化存储

scrapy持久化存储:

  • 基于终端指令:
    • 要求:只可以将parse方法的返回值存储到本地的文本文件中
    • 注意:持久化存储对应的文本文件类型只可以为:json、jsonlines、jl、csv、xml、marshal、pickle
    • 指令:scrapy crawl xxx -o filePath
    • 好处:简洁高效便捷
    • 缺点:局限性比较强(数据只可以存储到指定后缀的文本文件中)

5. 基于管道持久化存储操作

基于管道:

  • 编码流程:
    • 数据解析
    • 在item类中定义相关的属性
    • 将解析的数据封装到item类型的对象
    • 将item类型的对象提交给管道进行持久化存储的操作
    • 在管道类的process_item中要将其接收到的item对象中存储的数据进行持久化存储操作
    • 在配置文件中开启管道
  • 好处:
    • 通用性强。

面试题:将爬取到的数据一份存储到本地,一份存储到数据库,如何实现?

  • 管道文件中一个管道类对应的是将数据存储到一种平台
  • 爬虫文件提交的item只会给管道文件中第一个被执行的管道类接收
  • process_item中的return item表示将item传递给下一个即将被执行的管道类

6. 全站数据爬取

基于spider的全站数据爬取:就是将网站中某板块下的全部页码对应的页面数据进行爬取。

  • 爬取:校花网明星写真的名称
  • 实现方式:
    • 将所有页面的url添加到start_urls列表(不推荐)
    • 自行手动进行请求发送(推荐)
'''------------校花网xiaohua.py----------------'''
# -*- coding: utf-8 -*-
import scrapy

class XiaohuaSpider(scrapy.Spider):
    name = 'xiaohua'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['http://www.521609.com/tuku/mxxz/']

    #生成一个通用的url模板(不可变)
    url = 'http://www.521609.com/tuku/mxxz/index_%d.html'
    page_num = 2

    def parse(self, response):
        li_list = response.xpath('/html/body/div[4]/div[3]/ul/li')
        for li in li_list:
            img_name = li.xpath('./a/p/text()').extract_first()
            print(img_name)

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

'''---------------校花网pipelines.py--------------------'''
# -*- coding: utf-8 -*-

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

class XiaohuaproPipeline(object):
    def process_item(self, item, spider):
        return item

'''----------------校花网settings.py部分代码---------------------------'''
ROBOTSTXT_OBEY = False
LOG_LEVEL = 'ERROR'
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36'

7. 五大核心组件

五大核心组件:

  • Spiders:产生URL,对URL进行手动发送进行数据解析
  • 引擎(Scrapy Engine):数据流处理触发事务
  • 调度器(Scheduler):过滤器去重去重后的请求对象压到队列
  • 下载器(Downloader):负责获取页面数据并提供给引擎,而后提供给Spider
  • 项目管道(Item Pipeline):负责处理爬虫从网页中抽取的实体,页面被爬虫解析所需的数据存入item后,将被发送到管道,经过特定的次序处理数据,最后存入本地文件或者数据库。

8. 请求传参

  • 使用场景:如果爬取解析的数据不在同一张页面中。(深度爬取)
  • 需求:爬取boss的岗位名称和岗位描述
#### 我尝试着并未有啥结果.......等大佬
import scrapy
from bossPro.items import BossproItem

class BossSpider(scrapy.Spider):
    name = 'boss'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://www.zhipin.com/c100010000/?page=1&ka=page-1']

    url = 'https://www.zhipin.com/c100010000/?page=%d'
    page_num = 2

   #回调函数接收item
    def parse_detail(self,response):
        item = response.meta['item']

        job_desc = response.xpath('//*[@id="main"]/div[3]/div/div[2]/div[2]/div[1]/div//text()').extract()
        job_desc = ''.join(job_desc)
        print(job_desc)
        item['job_desc'] = job_desc

        yield item

    #解析首页中的岗位名称
    def parse(self, response):
        li_list = response.xpath('//*[@id="main"]/div/div[2]/ul/li')
        for li in li_list:
            item = BossproItem()

            job_name = li.xpath('.//div/div[1]/div[1]/div/div[1]/span[1]/a/text()').extract_first()
            item['job_name'] = job_name
            print(job_name)
            detail_url = 'https://www.zhipin.com' + li.xpath('.//div/div[1]/div[1]/div/div[1]/span[1]/a/@href').extract_first()
            #对详情页发请求获取详情页的页面源码数据
            #手动请求的发送
            #请求传参:meta={},可以将meta字典传递给请求对应的回调函数
            yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item})

        #分页操作
        if self.page_num <= 5:
            new_url = format(self.url%self.page_num)
            self.page_num += 1

            yield scrapy.Request(new_url,callback=self.parse)

9. scrapy图片爬取

图片数据爬取之ImagesPipline:

  • 基于scrapy爬取字符串类型的数据和爬取图片类型的数据区别?字符串:只需要基于xpath进行解析且提交管道进行持久化存储图片:xpath解析出图片的src属性值,单独的对图片地址发起请求获取二进制类型的数据
  • ImagesPipeline:只需要将img的src的属性值进行解析,提交到管道,管道就会对图片的src进行请求发送获取图片的二进制类型的数据,且还会帮我们进行持久化存储。
  • 需求:爬取站长素材的高清图片
  • 使用流程:数据解析(图片的地址)将存储图片地址的item提交到指定的管道类在管道文件中自己定制一个基于ImagesPipeLine的一个管道类get_media_request( )file_pathitem_completed在配置文件中操作指定图片存储目录:IMAGES_STORE = './imgs_ZYZhang'指定开启的管道:自定制的管道类
'''----------------爬取站长素材高清图片  img.py-----------------------'''
# -*- coding: utf-8 -*-
import scrapy
from imgsPro.items import ImgsproItem

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

    def parse(self, response):
        div_list = response.xpath('//div[@id="container"]/div')
        for div in div_list:
            #注意:使用伪属性 src2
            src = 'https:' + div.xpath('./div/a/img/@src2').extract_first()

            item = ImgsproItem()
            item['src'] = src

            yield item
'''----------------------爬取站长素材高清图片  pipelines.py---------------------------'''            
# -*- coding: utf-8 -*-

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

# class ImgsproPipeline(object):
#     def process_item(self, item, spider):
#         return item

from scrapy.pipelines.images import ImagesPipeline
import scrapy
class imgsPileLine(ImagesPipeline):

    #可以根据图片地址进行图片数据的请求
    def get_media_requests(self, item, info):

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

    #指定图片存储的路径
    def file_path(self, request, response=None, info=None):
        imgName = request.url.split('/')[-1]
        return imgName

    def item_completed(self, results, item, info):
        return item #返回给下一个即将被执行的管道类
'''---------------------------------爬取站长素材高清图片  items.py-----------------------------'''
# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html

import scrapy

class ImgsproItem(scrapy.Item):
    # define the fields for your item here like:
    src = scrapy.Field()
    # pass
'''------------------------------爬取站长素材高清图片 setting.py部分代码-------------------'''
#指定图片存储的目录
IMAGES_STORE = './imgs_ZYZhang'
ITEM_PIPELINES = {
   'imgsPro.pipelines.imgsPileLine': 300,
}
LOG_LEVEL = 'ERROR'
# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'

# Obey robots.txt rules
ROBOTSTXT_OBEY = False

10. 中间件

  • 下载中间件:位置:引擎和下载器之间作用:批量拦截到整个工程中所有的请求和响应拦截请求:UA伪装:process_request代{过}{滤}理IP:process_exception:return request拦截响应:篡改响应数据,响应对象网易新闻爬取

11. 网易新闻

需求:爬取网易新闻的新闻数据(标题和内容)

  • 通过网易新闻的首页解析出几大板块对应的详情页的url(经验证,无动态加载)
  • 每个板块点击后,其中的新闻标题都是动态加载出来的(动态加载)
  • 通过解析出每一条新闻详情页的url,获取详情页的页面源码,解析出新闻内容
'''-------------------------------网易新闻  wangyi.py------------------------'''
# -*- coding: utf-8 -*-
import scrapy
from selenium import webdriver
from wangyiPro.items import WangyiproItem
class WangyiSpider(scrapy.Spider):
    name = 'wangyi'
    # allowed_domains = ['www.cccom']
    start_urls = ['https://news.163.com/']
    models_urls = []  #存储五个板块对应详情页的url
    #解析五大板块对应详情页的url

    #实例化一个浏览器对象
    def __init__(self):
        self.bro = webdriver.Chrome(executable_path='F:\PythonProjects\爬虫\动态加载数据处理\chromedriver.exe')

    def parse(self, response):
        li_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li')
        alist = [3,4,6,7,8]
        for index in alist:
            model_url = li_list[index].xpath('./a/@href').extract_first()
            self.models_urls.append(model_url)

        #依次对每一个板块对应的页面进行请求
        for url in self.models_urls:      #对每一个板块的url进行请求发送
            yield scrapy.Request(url,callback=self.parse_model)

    #每一个板块对应的新闻标题相关的内容都是动态加载
    def parse_model(self,response):    #解析每一个板块页面中对应新闻的标题和新闻详情页的url
        # response.xpath()
        div_list = response.xpath('/html/body/div/div[3]/div[4]/div[1]/div/div/ul/li/div/div')
        for div in div_list:
            title = div.xpath('./div/div[1]/h3/a/text()').extract_first()
            new_detail_url = div.xpath('./div/div[1]/h3/a/@href').extract_first()

            item = WangyiproItem()
            item['title'] = title

            #对新闻详情页的url发起请求
            yield scrapy.Request(url=new_detail_url, callback=self.parse_detail, meta={'item': item})
    def parse_detail(self,response):       # 解析新闻内容
        content = response.xpath('//*[@id="content"]/div[2]//text()').extract()
        content = ''.join(content)
        item = response.meta['item']
        item['content'] = content

        yield item

    def closed(self, spider):
        self.bro.quit()

'''-------------------------------网易新闻  pipelines.py-----------------------------------'''
# -*- coding: utf-8 -*-

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

class WangyiproPipeline(object):
    def process_item(self, item, spider):
        print(item)
        return item
'''-------------------------------网易新闻  middlewares.py-------------------------'''
# -*- coding: utf-8 -*-

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

from scrapy import signals

from scrapy.http import HtmlResponse
from time import sleep
class WangyiproDownloaderMiddleware(object):
    # Not all methods need to be defined. If a method is not defined,
    # scrapy框架 acts as if the downloader middleware does not modify the
    # passed objects.

    def process_request(self, request, spider):
        # Called for each request that goes through the downloader
        # middleware.

        # Must either:
        # - return None: continue processing this request
        # - or return a Response object
        # - or return a Request object
        # - or raise IgnoreRequest: process_exception() methods of
        #   installed downloader middleware will be called
        return None

    # 通过该方法拦截五大板块对应的响应对象,进行篡改,使其满足需求
    def process_response(self, request, response, spider):    #spider爬虫对象
        bro = spider.bro  #获取了在爬虫类中定义的浏览器对象

        #挑选出指定的响应对象进行篡改
        #    通过url指定request
        #    通过request指定response
        if request.url in spider.models_urls:
            bro.get(request.url)   #五个板块对应的url进行请求
            sleep(3)
            page_text = bro.page_source  #包含了动态加载的新闻数据

            #response #五大板块对应的响应对象
            #针对定位到的这些response进行篡改
            #实例化一个新的响应对象(符合需求:包含动态加载出的新闻数据),替代原来旧的响应对象
            #如何获取动态加载出的新闻数据?
                #基于selenium便捷的获取动态加载数据
            new_response = HtmlResponse(url=request.url, body=page_text, encoding='utf-8', request=request)

            return new_response
        else:
            #response #其他请求对应的响应对象
            return response

    def process_exception(self, request, exception, spider):
        # Called when a download handler or a process_request()
        # (from other downloader middleware) raises an exception.

        # Must either:
        # - return None: continue processing this exception
        # - return a Response object: stops process_exception() chain
        # - return a Request object: stops process_exception() chain
        pass
'''-----------------------------网易新闻 setting.py部分代码---------------------------------'''
#USER_AGENT = 'wangyiPro (+http://www.yourdomain.com)'
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36'

# Obey robots.txt rules
ROBOTSTXT_OBEY = False
# Enable or disable downloader middlewares
# See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html
DOWNLOADER_MIDDLEWARES = {
   'wangyiPro.middlewares.WangyiproDownloaderMiddleware': 543,
}
ITEM_PIPELINES = {
   'wangyiPro.pipelines.WangyiproPipeline': 300,
}
LOG_LEVEL = 'ERROR'

12. CrawlSpider的全站数据爬取

CrawlSpider:基于Spider的一个子类

  • 全站数据爬取的方式
    • 基于Spider:手动请求发送
    • 基于CrawlSpider
  • CrawlSpider的使用:
    • 创建一个工程
    • cd XXX
    • 创建爬虫文件(CrawlSpider)
      • scrapy genspider -t crawl xxx www.xxxx.com
      • 链接提取器(LinkExtractor):根据指定规则(allow="正则")进行指定链接的提取
      • 规则解析器(Rule):将链接提取器提取到的链接进行指定规则(callback)的解析操作
  • 需求:爬取阳光热线网站中的编号,新闻标题,新闻内容,标号分析:爬取的数据没有在同一张页面中可以使用链接提取器提取所有的页码链接让链接提取器提取所有的问政详情页链接
'''---------------------阳光问政    sun.py---------------------------'''
'''网站页面源码跟视频课有改动,建议follow先改False爬一下,不然容易被封IP,有兴趣的可以改改,搞个代{过}{滤}理啥的再爬'''
# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from sunPro.items import SunproItem, DetailItem

# 需求:爬取阳光热线网站中的编号,新闻标题,新闻内容,标号
class SunSpider(CrawlSpider):
    name = 'sun'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['http://wz.sun0769.com/political/index/politicsNewest?id=1&page=']

    #链接提取器:根据指定规则(allow="正则")进行指定链接的提取
    link = LinkExtractor(allow=r'id=1&page=\d+')
    link_detail = LinkExtractor(allow=r'index\?id=\d+')
    rules = (
        #规则解析器:将链接提取器提取到的链接进行指定规则(callback)的解析操作
        Rule(link, callback='parse_item', follow=False),
        #follow=True:可以将链接提取器 继续作用到 链接提取器提取到的链接 所对应的页面中
        Rule(link_detail, callback='parse_detail')
    )
    #http://wz.sun0769.com/political/politics/index?id=490505
    #http://wz.sun0769.com/political/politics/index?id=490504

    # 解析新闻编号和新闻的标题
    # 如下两个解析方法中是不可以实现请求传参!
    # 无法将两个解析方法解析的数据存储到同一个item中,可以依次存储到两个item中
    def parse_item(self, response):
        #注意:xpath表达式中不可以出现tbody标签
        li_list = response.xpath('/html//div[2]/div[3]/ul[2]/li')
        for li in li_list:
            new_num = li.xpath('./span[1]/text()').extract_first()
            new_title = li.xpath('./span[3]/a/text()').extract_first()

            item = SunproItem()
            item['title'] = new_title
            item['new_num'] = new_num

            yield item

    #解析新闻内容和新闻编号
    def parse_detail(self,response):
        new_id = response.xpath('/html//div[3]/div[2]/div[2]/div[1]/span[4]/text()').extract_first().strip().replace("\r\n", "").replace(" ", "")
        new_content = response.xpath('/html//div[3]/div[2]/div[2]/div[2]/pre/text()').extract()
        new_content = ''.join(new_content)

        # print(new_id,new_content)
        item = DetailItem()
        item['content'] = new_content
        item['new_id'] = new_id

        yield item

'''-------------------------------pipelines.py------------------------------'''
# -*- coding: utf-8 -*-

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

class SunproPipeline(object):
    def process_item(self, item, spider):
        #如何判定item的类型
        #将数据写入数据库时,如何保证数据的一致性
        if item.__class__.__name__ == 'DetailItem':
            print(item['new_id'],item['content'])
        else:
            print(item['new_num'],item['title'])
        return item

'''---------------------------items.py----------------------'''
# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html

import scrapy

class SunproItem(scrapy.Item):
    # define the fields for your item here like:
    title = scrapy.Field()
    new_num = scrapy.Field()

class DetailItem(scrapy.Item):
    new_id = scrapy.Field()
    content = scrapy.Field()

13. 分布式概述及搭建

分布式爬虫:

  • 概念:我们需要搭建一个分布式的机群,让其对一组资源进行分布联合爬取。
  • 作用:提升爬取数据的效率

如何实现分布式?

  • 安装一个scrapy-redis的组件
  • 原生的scrapy是不可以实现分布式爬虫的,必须要让scrapy-redis组件一起实现分布式爬虫。

为什么原生的scrapy不可以实现分布式?

  • 调度器不可以被分布式机群共享
  • 管道不可以被分布式机群共享

scrapy-redis组件作用:

  • 可以给原生的scrapy框架提供可以被共享的管道调度器

scrapy-redis实现流程:

  • 创建一个工程
  • 创建一个基于CrawlSpider的爬虫文件
  • 修改当前的爬虫文件:导包:from scrapy_redis.spiders import RedisCrawlSpider将start_urls和allowed_domains进行注释添加一个新属性:redis_key = ' ' 可以被共享的调度器队列的名称编写数据解析相关的操作将当前爬虫类的父类修改成 RedisCrawlSpider
  • 修改配置文件settings指定使用可以被共享的管道:ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 400 }指定调度器:增加了一个去重容器类的配置,作用是用Redis的set集合来存储请求的指纹数据,从而实现请求去重的持久化DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"使用scrapy-redis组件自己的调度器SCHEDULER = "scrapy_redis.scheduler.Scheduler"配置调度器是否要持久化,也就是当爬虫结束了,要不要清空Redis中请求队列和去重指纹的set。如果是True,就表示要持久化存储,就不清数据,否则清空数据SCHEDULER_PERSIST = True指定redis服务器
  • redis相关操作配置:配置redis的配置文件:linux或者mac:redis.confwindows:redis.windows.conf打开配置文件修改:将bind 127.0.0.1进行注释或删除关闭保护模式:protected-mode yes改为no结合着配置文件开启redis服务redis-server 配置文件启动客户端:redis-cli
  • 执行工程:scrapy runspider xxx.py
  • 向调度器的队列中放入一个起始的url:调度器的队列在redis的客户端中lpush xxx www.xxx.com
  • 爬取到的数据存储在了 redis 的 proName:items 这个数据结构中

14. 增量式爬虫

  • 概念:监测网站数据更新的情况,只会爬取网站最新更新出来的数据。
  • 分析:指定一个起始url基于CrawlSpider获取其他页码链接基于Rule将其他页码链接进行请求从每一个页码对应的页面源码中解析出每一个电影详情页的URL核心:检测电影详情页的url之前有没有请求过将爬取过的电影详情页的url存储存储到redis的set数据结构对详情页的url发起请求,然后解析出电影的名称和简介进行持久化存储

九、补充——异步编程

为什么要讲?

  • 这一部分的知识点不太容易学习(异步非阳塞、 asyncio)
  • 异步相关话题和框架越来越多,例如:tornado、fastapi、django 3.x asgi、aiohttp都在异步→提升性能

如何讲解?

  • 第一部分:协程
  • 第二部分:asyncio模块进行异步编程
  • 第三部分:实战案例

1. 协程

协程不是计算机提供,程序员人为创造。

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

def func1():
    print(1)
    ...
    print(2)
def func2():
    print(3)
    ...
    print(4)
func1()
func2()

实现协程的集中方法:

  • greelet,早期模块
  • yield关键字
  • asyncio装饰器(py3.4及以后版本)
  • async、await关键字(py3.5及以后版本)

(1)greenlet实现协程

pip install greenlet

from greenlet import greenlet
def func1():
    print(1)
    gr2.switch()        #切换到func2函数
    print(2)
    gr2.switch()        #切换到func2函数,从上一次执行的位置继续向后执行
def func2():
    print(3)
    gr1.switch()        #切换到func1函数,从上一次执行的位置继续向后执行
    print(4)
gr1 = greenlet(func1)
gr2 = greenlet(func2)
gr1.switch()                #去执行func1函数

相关推荐

Micheal Nielsen&#39;s神经网络学习之二

依然是跟着MichaelNielsen的神经网络学习,基于前一篇的学习,已经大概明白了神经网络的基本结构和BP算法,也能通过神经网络训练数字识别功能,之后我试验了一下使用神经网络训练之前的文本分类,...

CocoaPods + XCTest进行单元测试 c单元测试工具

在使用XCTest进行单元测试时,我们经常会遇到一些CocoaPods中的开源框架的调用,比如“Realm”或“Alamofire”在测试的时候,如果配置不当,会导致“frameworknotfo...

Java基础知识回顾第四篇 java基础讲解

1、&和&&的区别作为逻辑运算符:&(不管左边是什么,右边都参与运算),&&(如果左边为false,右边则不参与运算,短路)另外&可作为位运算符...

项目中的流程及类似业务的设计模式总结

说到业务流程,可能是我做过的项目中涉及业务最多的一个方面了。除了在流程设计之外,在一些考核系统、产业审批、还有很多地方,都用到相似的设计思路,在此一并总结一下。再说到模式,并不是因为流行才用这个词,而...

联想三款显示器首批获得 Eyesafe Certified 2.0 认证

IT之家7月31日消息,据外媒报道,三款全新联想显示器是全球首批满足EyesafeCertified2.0的设备。据报道,联想获得EyesafeCertified2.0认证的显...

maven的生命周期,插件介绍(二) 一个典型的maven构建生命周期

1.maven生命周期一个完整的项目构建过程通常包括清理、编译、测试、打包、集成测试、验证、部署等步骤,Maven从中抽取了一套完善的、易扩展的生命周期。Maven的生命周期是抽象的,其中的具体任务都...

多线程(3)-基于Object的线程等待与唤醒

概述在使用synchronized进行线程同步中介绍了依赖对象锁定线程,本篇文章介绍如何依赖对象协调线程。同synchronized悲观锁一样,线程本身不能等待与唤醒,也是需要对象才能完成等待与唤醒的...

jquery mobile + 百度地图 + phonegap 写的一个&quot;校园助手&quot;的app

1jquerymobile+百度地图+phonegap写的一个"校园助手"的app,使用的是基于Flat-UI的jQueryMobile,请参考:https://github.com/...

Apache 服务启动不了 apache系统服务启动不了

{我是新手,从未遇到此问题,请各位大大勿喷}事由:今天早上上班突然发现公司网站出现问题。经过排查,发现是Apache出现问题。首先检查配置文件没有出问题后,启动服务发现Apache服务能启动,但是没法...

健康债和技术债都不能欠 公众号: 我是攻城师(woshigcs)

在Solr4.4之后,Solr提供了SolrCloud分布式集群的模式,它带来的主要好处是:(1)大数据量下更高的性能(2)更好扩展性(3)更高的可靠性(4)更简单易用什么时候应该使用Sol...

Eye Experience怎么用?HTC告诉你 eyebeam怎么用

IT之家(www.ithome.com):EyeExperience怎么用?HTC告诉你HTC上周除了发布HTCDesireEYE自拍机和HTCRE管状运动相机之外,还发布了一系列新的智能手机...

Android系统应用隐藏和应用禁止卸载

1、应用隐藏与禁用Android设置中的应用管理器提供了一个功能,就是【应用停用】功能,这是针对某些系统应用的。当应用停用之后,应用的图标会被隐藏,但apk还是存在,不会删除,核心接口就是Packag...

计算机软件技术分享--赠人玫瑰,手遗余香

一、Netty介绍Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。也就是说,Netty...

Gecco爬虫框架的线程和队列模型 爬虫通用框架

简述爬虫在抓取一个页面后一般有两个任务,一个是解析页面内容,一个是将需要继续抓取的url放入队列继续抓取。因此,当爬取的网页很多的情况下,待抓取url的管理也是爬虫框架需要解决的问题。本文主要说的是g...

一点感悟(一) 初识 初读感知的意思

时间过得很快,在IT业已从业了两年多。人这一辈子到底需要什么,在路边看着人来人往,大部分人脸上都是很匆忙。上海真是一个魔都,它有魅力,有底蕴,但是一个外地人在这里扎根置业,真的是举全家之力,还贷3...