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

385 阅读17分钟

3. 模拟登录cookie操作

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

session会话对象:

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 = [ 'downsc.chinaz.net/Files/DownL…', 'downsc.chinaz.net/Files/DownL…', 'downsc.chinaz.net/Files/DownL…' ]

    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('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('www.runoob.com/try/try.php…')

    如果定位的标签是存在与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('kyfw.12306.cn/otn/resourc…')

    #最大化浏览器窗口 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 = ['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: doc.scrapy.org/en/latest/t…

    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 = ['www.zhipin.com/c100010000/…']

    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 = ['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: doc.scrapy.org/en/latest/t…

    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:

    doc.scrapy.org/en/latest/t…

    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 = ['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: doc.scrapy.org/en/latest/t…

    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:

    doc.scrapy.org/en/latest/t…

    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 (+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 doc.scrapy.org/en/latest/t…

    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)的解析操作

  • **需求:**爬取阳光热线网站中的编号,新闻标题,新闻内容,标号分析:爬取的数据没有在同一张页面中可以使用链接提取器提取所有的页码链接让链接提取器提取所有的问政详情页链接