python爬虫-Day06

本文深入探讨Python中的多进程概念与应用,包括进程的创建、管理和并发执行,对比多线程与多进程的优劣。同时,详细介绍Scrapy框架的使用,包括安装、配置、爬虫编写及数据存储流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

进程

进程的概念
python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程。

进程的概念:
	 进程是程序的一次执行过程, 正在进行的一个过程或者说一个任务,而负责执行任务的则是CPU.
	
进程的生命期:
	当操作系统要完成某个任务时,它会创建一个进程。当进程完成任务之后,系统就会撤销这个进程,收回它所占用的资源。从创建到撤销的时间段就是进程的生命期

进程之间存在并发性:
	在一个系统中,同时会存在多个进程。他们轮流占用CPU和各种资源

并行与并发的区别:
	无论是并行还是并发,在用户看来都是同时运行的,不管是进程还是线程,都只是一个任务而已, 
真正干活的是CPU,CPU来做这些任务,而一个cpu(单核)同一时刻只能执行一个任务。 
并行:多个任务同时运行,只有具备多个cpu才能实现并行,含有几个cpu,也就意味着在同一时刻可以执行几个任务。 
	CPU数量 >= 任务数量
并发:是伪并行,即看起来是同时运行的,实际上是单个CPU在多道程序之间来回的进行切换。
	CPU数量 < 任务数量

同步与异步的概念:
	同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去。 
	异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进行处理,这样可以提高执行的效率。 
	比如:打电话的过程就是同步通信,发短信时就是异步通信。

多线程和多进程的关系:
	对于计算密集型应用,应该使用多进程;
	对于IO密集型应用,应该使用多线程。线程的创建比进程的创建开销小的多。

创建进程
使用multiprocessing.Process
import multiprocessing
import time

def func(arg):
    pname = multiprocessing.current_process().name
    pid = multiprocessing.current_process().pid
    print("当前进程ID=%d,name=%s" % (pid, pname))

    for i in range(5):
        print(arg)
        time.sleep(1)

if __name__ == "__main__":
    p = multiprocessing.Process(target=func, args=("hello",))
    # p.daemon = True  # 设为【守护进程】(随主进程的结束而结束)
    p.start()

    while True:
        print("子进程是否活着?", p.is_alive())
        time.sleep(1)
    print("main over")

通过继承Process实现自定义进程
import multiprocessing
import os

# 通过继承Process实现自定义进程
class MyProcess(multiprocessing.Process):
    def __init__(self, name, url):
        super().__init__()
        self.name = name
        self.url = url  # 自定义属性

    # 重写run
    def run(self):
        pid = os.getpid()
        ppid = os.getppid()
        pname = multiprocessing.current_process().name
        print("当前进程name:", pname)
        print("当前进程id:", pid)
        print("当前进程的父进程id:", ppid)

if __name__ == '__main__':
	
    # 创建3个进程
    MyProcess("小分队1", "").start()
    MyProcess("小分队2", "").start()
    MyProcess("小分队3", "").start()
    print("主进程ID:", multiprocessing.current_process().pid) 

    # CPU核数 
    coreCount = multiprocessing.cpu_count()
    print("我的CPU是%d核的" % coreCount)

    # 获取当前活动的进程列表
    print(multiprocessing.active_children()) 
同步异步和进程锁
import multiprocessing
import random
import time

def fn():
    name = multiprocessing.current_process().name
    print("开始执行进程:", name)
    time.sleep(random.randint(1, 4))
    print("执行结束:", name)

# 多进程
# 异步执行进程
def processAsync():
    p1 = multiprocessing.Process(target=fn, name="小分队1")
    p2 = multiprocessing.Process(target=fn, name="小分队2")
    p1.start()
    p2.start()

# 同步执行
def processSync():
    p1 = multiprocessing.Process(target=fn, name="小分队1")
    p2 = multiprocessing.Process(target=fn, name="小分队2")
    p1.start()
    p1.join()
    p2.start()
    p2.join()

# 加锁
def processLock():
    # 进程锁
    lock = multiprocessing.Lock()
    p1 = multiprocessing.Process(target=fn2, name="小分队1", args=(lock,))
    p2 = multiprocessing.Process(target=fn2, name="小分队2", args=(lock,))
    p1.start()
    p2.start()

def fn2(lock):
    name = multiprocessing.current_process().name
    print("开始执行进程:", name)

    # 加锁
    # 方式一
    # if lock.acquire():
    #     print("正在工作...")
    #     time.sleep(random.randint(1, 4))
    #     lock.release()

    # 方式二
    with lock:
        print("%s:正在工作..." % name)
        time.sleep(random.randint(1, 4))

    print("%s:执行结束:"% name)


if __name__ == '__main__':
    # processAsync() # 异步执行
    # processSync()  # 同步执行
    processLock()  # 加进程锁

使用Semaphore控制进程的最大并发
import multiprocessing
import time

def fn(sem):
    with sem:
        name = multiprocessing.current_process().name
        print("子线程开始:", name)
        time.sleep(3)
        print("子线程结束:", name)

if __name__ == '__main__':
    sem = multiprocessing.Semaphore(3)
    for i in range(8):
        multiprocessing.Process(target=fn, name="小分队%d"%i, args=(sem, )).start()

练习: 多进程抓取链家 https://sz.lianjia.com/ershoufang/rs/
练习: 多进程+多协程抓取链家 https://sz.lianjia.com/ershoufang/rs/
练习: 多线程分页抓取斗鱼妹子 https://www.douyu.com/gapi/rkc/directory/2_201/4
练习: 多进程分页抓取斗鱼妹子 https://www.douyu.com/gapi/rkc/directory/2_201/4

扩展

线程池
import threading
import threadpool

import time
import random

# ================================================================= #
def fn(who):
    tname = threading.current_thread().getName()

    print("%s开始%s..." % (tname, who))
    time.sleep(random.randint(1, 5))
    print("-----%s,%s-----" % (tname, who))

# ================================================================= #
# 请求执行结束回调
# request=已完成的请求
# result=任务的返回值
def cb(request, result):
    print("cb", request, result)

if __name__ == '__main__':

    # 创建一个最大并发为4的线程池(4个线程)
    pool = threadpool.ThreadPool(4)

    argsList = ["张三丰", "赵四", "王五", "六爷", "洪七公", "朱重八"]
    # 允许回调
    requests = threadpool.makeRequests(fn, argsList, callback=cb)

    for req in requests:
        pool.putRequest(req)

    # 阻塞等待全部请求返回(线程池创建的并发默认为【守护线程】)
    pool.wait()
    print("Over")


进程池
import multiprocessing
import random
import time

def fn1(arg, name):
    print("正在执行任务1: {}...".format(arg))
    time.sleep(random.randint(1, 5))
    print("进程%d完毕!" % (name))

def fn2(arg, name):
    print("正在执行任务2: {}...".format(arg))
    time.sleep(random.randint(1, 5))
    print("进程%d完毕!" % (name))


# 回调函数
def onback(result):
    print("得到结果{}".format(result))

if __name__ == "__main__":
    # 待并发执行的函数列表
    funclist = [fn1, fn2, fn1, fn2]

    # 创建一个3并发的进程池
    pool = multiprocessing.Pool(3)

    # 遍历函数列表,将每一个函数丢入进程池中
    for i in range(len(funclist)):
        # 同步执行
        # pool.apply(func=funclist[i], args=("hello", i))
        # 异步执行
        pool.apply_async(func=funclist[i], args=("hello", i), callback=onback)

    pool.close()  # 关闭进程池,不再接收新的进程
    pool.join()  # 令主进程阻塞等待池中所有进程执行完毕

Scrapy 框架介绍

Scrapy是用纯Python实现一个为了爬取网站数据、提取结构性数据而编写的应用框架,用途非常广泛。
Scrapy框架:用户只需要定制开发几个模块就可以轻松的实现一个爬虫,用来抓取网页内容以及各种图片,非常之方便。
Scrapy 使用了Twisted(其主要对手是Tornado)多线程异步网络框架来处理网络通讯,可以加快我们的下载速度,不用自己去实现异步框架,并且包含了各种中间件接口,可以灵活的完成各种需求。
Scrapy架构图

image

Scrapy主要包括了以下组件:
	Scrapy Engine(引擎): 
        负责Spider、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等。

	Scheduler(调度器): 
        它负责接受`引擎`发送过来的Request请求,并按照一定的方式进行整理排列,入队,当`引擎`需要时,交还给`引擎`。

	Downloader(下载器):
    	负责下载`Scrapy Engine(引擎)`发送的所有Requests请求,并将其获取到的Responses交还给`Scrapy Engine(引擎)`,由`引擎`交给`Spider`来处理,

	Spider(爬虫):
    	它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给`引擎`,再次进入`Scheduler(调度器)`,

	Item Pipeline(管道):
    	它负责处理`Spider`中获取到的Item,并进行后期处理(详细分析、过滤、存储等)的地方.

	Downloader Middlewares(下载中间件):
		你可以当作是一个可以自定义扩展下载功能的组件。

	Spider Middlewares(Spider中间件):
    	你可以理解为是一个可以自定扩展和操作`引擎`和`Spider`中间`通信`的功能组件(比如进入`Spider`的Responses和从`Spider`出去的Requests)
        

安装Scrapy

Scrapy的安装介绍
	Scrapy框架官方网址:http://doc.scrapy.org/en/latest
	Scrapy中文维护站点:http://scrapy-chs.readthedocs.io/zh_CN/latest/index.html
	
安装方式:   
	1、安装wheel
		pip install wheel
    2、安装lxml
		pip install lxml
    3、安装pyopenssl
		pip install pyopenssl
    4、安装Twisted
		需要我们自己下载Twisted,然后安装。这里有Python的各种依赖包。选择适合自己Python以及系统的Twisted版本:https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
		# 3.6版本(cp后是python版本)
         pip install Twisted-18.9.0-cp36-cp36m-win_amd64.whl
		
    5、安装pywin32
		pip install pywin32
    6、安装scrapy
        pip install scrapy
        
安装后,只要在命令终端输入scrapy来检测是否安装成功

使用Scrapy

使用爬虫可以遵循以下步骤:
  1. 创建一个Scrapy项目
  2. 定义提取的Item
  3. 编写爬取网站的 spider 并提取 Item
  4. 编写 Item Pipeline 来存储提取到的Item(即数据)
1. 新建项目(scrapy startproject)
创建一个新的Scrapy项目来爬取 http://www.meijutt.com/new100.html 中的数据,使用以下命令:
scrapy startproject meiju
创建爬虫程序
cd meiju
scrapy genspider meijuSpider meijutt.com

其中:
	meijuSpider为爬虫文件名
	meijutt.com为爬取网址的域名

创建Scrapy工程后, 会自动创建多个文件,下面来简单介绍一下各个主要文件的作用:

scrapy.cfg:
	项目的配置信息,主要为Scrapy命令行工具提供一个基础的配置信息。(真正爬虫相关的配置信息在settings.py文件中)
items.py:
	设置数据存储模板,用于结构化数据,如:Django的Model
pipelines:
	数据处理行为,如:一般结构化的数据持久化
settings.py:
	配置文件,如:递归的层数、并发数,延迟下载等
spiders:
	爬虫目录,如:创建文件,编写爬虫规则
    
注意:一般创建爬虫文件时,以网站域名命名
2. 定义Item

​ Item是保存爬取到的数据的容器;其使用方法和python字典类似,虽然我们可以在Scrapy中直接使用dict,但是 Item提供了额外保护机制来避免拼写错误导致的未定义字段错误;

​ 类似ORM中的Model定义字段,我们可以通过scrapy.Item 类来定义要爬取的字段。

import scrapy

class MeijuItem(scrapy.Item):
    name = scrapy.Field()
3. 编写爬虫
# -*- coding: utf-8 -*-
import scrapy
from lxml import etree
from meiju.items import MeijuItem

class MeijuspiderSpider(scrapy.Spider):
    # 爬虫名
    name = 'meijuSpider'
    # 被允许的域名
    allowed_domains = ['meijutt.com']
    # 起始爬取的url
    start_urls = ['http://www.meijutt.com/new100.html']

    # 数据处理
    def parse(self, response):
        # response响应对象
        # xpath
        mytree = etree.HTML(response.text)
        movie_list = mytree.xpath('//ul[@class="top-list  fn-clear"]/li')

        for movie in movie_list:
            name = movie.xpath('./h5/a/text()')

            # 创建item(类字典对象)
            item = MeijuItem()
            item['name'] = name
            yield item

启用一个Item Pipeline组件

为了启用Item Pipeline组件,必须将它的类添加到 settings.py文件ITEM_PIPELINES 配置修改settings.py,并设置优先级,分配给每个类的整型值,确定了他们运行的顺序,item按数字从低到高的顺序,通过pipeline,通常将这些数字定义在0-1000范围内(0-1000随意设置,数值越低,组件的优先级越高)

ITEM_PIPELINES = {
   'meiju.pipelines.MeijuPipeline': 300,
}
设置UA

在setting.py中设置USER_AGENT的值

USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'

4. 编写 Pipeline 来存储提取到的Item(即数据)
class SomethingPipeline(object):
    def init(self):    
        # 可选实现,做参数初始化等
        
	def process_item(self, item, spider):
        # item (Item 对象) – 被爬取的item
        # spider (Spider 对象) – 爬取该item的spider
        # 这个方法必须实现,每个item pipeline组件都需要调用该方法,
        # 这个方法必须返回一个 Item 对象,被丢弃的item将不会被之后的pipeline组件所处理。
        return item

    def open_spider(self, spider):
        # spider (Spider 对象) – 被开启的spider
        # 可选实现,当spider被开启时,这个方法被调用。

    def close_spider(self, spider):
        # spider (Spider 对象) – 被关闭的spider
        # 可选实现,当spider被关闭时,这个方法被调用
    
运行爬虫:
scrapy crawl meijuSpider

# nolog模式
scrapy crawl meijuSpider --nolog  

scrapy保存信息的最简单的方法主要有这几种,-o 输出指定格式的文件,命令如下:

scrapy crawl meijuSpider -o meiju.json
scrapy crawl meijuSpider -o meiju.csv
scrapy crawl meijuSpider -o meiju.xml
练习: Scrapy爬取新浪新闻存入数据库 http://roll.news.sina.com.cn/news/gnxw/gdxw1/index_1.shtml
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值