비동기 크롤러 -aiohttp 라이브러리, Twisted 라이브러리 소개

비동기 크롤러를 사용하는 이유는 무엇입니까?

 크롤러는 기본적으로 클라이언트와 서버 간의 통신 프로세스를 시뮬레이션합니다. 브라우저 측의 크롤러를 예로 들어 보겠습니다. 다른 웹 페이지를 크롤링하는 과정에서 URL에 따라 크롤링 할 많은 HTTP 요청을 구성해야합니다. 단일 스레드를 참조 개체로 사용하는 경우 코딩 습관이 일반적으로 채택은 동기 모드를 기반으로합니다. 예, 즉 이러한 요청을 직렬 방식으로 실행하려면 하나의 URL 크롤링이 종료 된 후에 만 ​​다음 URL이 크롤링됩니다. 네트워크 IO의 지연으로 인해 효율성이 매우 낮습니다. .
 어떤 사람들은 여기에서 다중 프로세스 + 다중 스레드를 사용하여 효율성을 향상시킬 수 있으며 비동기 프로그래밍을 사용하는 이유는 결국 비동기 프로그래밍이 프로그래밍의 어려움을 크게 증가시킬 것이라고 말할 수 있습니다. [ 프로세스, 스레드 및 코 루틴의 단순 정렬 ]이 마무리 기사에서 언급했듯이 다중 프로세스 / 다중 스레드는 효율성을 향상시킬 수 있지만 프로세스 / 스레드 간 전환시 많은 리소스를 소비합니다. IO 집약적 인 작업의 경우 다중 스레드 동시성을 사용하면 CUP의 사용률이 증가하고 크롤링 효율성이 향상 될 수 있지만 여전히 IO 차단 문제는 해결되지 않습니다. 다중 프로세스 든 다중 스레드 든 운영 체제는 IO가 차단되면 CPU의 실행 권한을 강제로 박탈하므로 크롤러의 실행 효율성이 저하됩니다. 비동기 프로그래밍에서 우리는 애플리케이션 수준에서 IO 차단을 감지 한 다음 실행할 다른 작업으로 적극적으로 전환하여 크롤러의 IO를 "낮추고"크롤러를 준비 상태로 만들어 운영 체제가 CPU를 허용하도록합니다. 크롤러를 최대한 활용하여 크롤러의 크롤링 효율성을 개선합니다.

보충 : 일반적인 IO 모델에는 차단, 비 차단, IO 멀티플렉싱 및 비동기가 포함됩니다. 이 네 가지 장면을 간략하게 설명하는 작은 밤입니다.
행복한 코딩 시간이 끝나면 여자 친구없는 독신은 좋은 친구와 서머너 캐년 맥스톤에 약속 만 잡을 수 있습니다. 내가 행복한 바람의 남자를 몇 초만에 선택하면 "Yasuo mid laner,주지 않으면 돌려 보내줘 it. ", 팀원들의 웃음 속에 로딩 인터페이스로 들어가지만 샤오 바왕을 만나면 로딩 속도가 비정상적으로 느립니다. . . 지금!

  1. 아무것도하지 않기로 선택하고 조수 소녀의 원래 진 소녀 그림을 똑바로보고 로딩이 완료 될 때까지 기다립니다. 이것은 차단입니다.
  2. 게임을보기 위해 특정 물고기로 전환하기로 선택했지만 로딩이 완료되었는지 확인하기 위해 때때로 LOL로 전환해야합니다. 이렇게 앞뒤로 이동하고, 죽을 정도로 피곤하고, 많은 것을 놓쳤습니다. 멋진 사진. 이것은 비 차단입니다.
  3. Love Crazy XL을 꺼내고 특정 물고기 앱을 열어 게임을 시청하므로 다시 전환하는 데 사용하지 않고 가끔 컴퓨터 모니터를보고 로딩이 완료되었는지 확인합니다. 이것은 IO 멀티플렉싱입니다.
  4. 인스턴트라면조차도 한 번에 소스가 다 떨어지기를 꺼려합니다. 정말 사랑스러운 XL은 없지만 코드 농부의 존엄성은이를 감지하면 브라우저로 자동 전환되는 작은 프로그램을 작성할 수 있습니다. 게임로드 중입니다., 특정 피쉬 스타 라이브 방송실을 엽니 다. 게임이로드되면 자동으로 LOL 게임 인터페이스로 다시 전환됩니다. 매끄러운 느낌을 매끄럽게 전환합니다. 이것을 비동기라고합니다.

주제 입력을 시작합시다

asyncio

aiohttp, tornado, twisted를 도입하기 전에 먼저 python3.4에 도입 된 표준 라이브러리를 이해합시다 asyncio. IO (네트워크 IO 만 해당)를 감지하고 애플리케이션 수준 스위칭을 구현하는 데 도움이 될 수 있습니다. 프로그래밍 모델은 메시지 루프입니다. asyncio모듈 에서 직접 EventLoop참조 가져온 다음 실행을 위해 실행해야하는 코 루틴을 던져서 EventLoop비동기 IO가 실현됩니다.

기본 사용

 

import asyncio
import random
import datetime


urls=['www.baidu.com','www.qq.com','www.douyu.com']

@asyncio.coroutine
def crawl(url):
    print("正在抓取:{}-{}".format(url,datetime.datetime.now().time()))
    io_time = random.random()*3 #随机模拟网络IO时间
    yield from asyncio.sleep(io_time) #模拟网络IO
    print('{}-抓取完成,用时{}s'.format(url,io_time))

loop = asyncio.get_event_loop() #获取EventLoop
loop.run_until_complete(asyncio.wait(map(crawl,urls))) #执行coroutine
loop.close()

작업 결과 :

 

正在抓取:www.baidu.com-12:45:26.517226
正在抓取:www.douyu.com-12:45:26.517226
正在抓取:www.qq.com-12:45:26.517226
www.douyu.com-抓取完成,用时0.1250027573049739s
www.baidu.com-抓取完成,用时0.450045918339271s
www.qq.com-抓取完成,用时0.6967129499714361s
[Finished in 0.9s]

실행 중에는 세 개의 요청이 거의 동시에 발행되고 반환 순서는 네트워크 IO 완료 시간의 순서를 기반으로한다는 것을 알 수 있습니다.

asyncio는 주로 TCP / UDP 소켓 통신에 사용되며 http 요청을 직접 보낼 수 없기 때문에 http 헤더를 직접 정의해야합니다.
보충:

  • 클라이언트가 서버에 HTTP 요청을 보내는 요청 메시지에는 요청 줄 , 메시지 헤더요청 본문 형식이 포함됩니다 .
    예 :GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n
  • asyncio제공 하나, @asyncio.coroutine당신은 할 수 있습니다 generatorA와 하나의 표시 coroutine유형을 한 다음 다른 전화를 coroutine내부적으로 비동기 작업을 구현합니다. 단순화하고 더 나은에서 비동기 IO 확인하기 위해 새 구문의 도입의 시작 부분 할 수 코드가 더 간결하고 읽기.yield fromcoroutinePython 3.5asyncawaitcoroutine

개념 보충

  • event_loop: 이벤트 루프는 무한 루프와 동일하며이 이벤트 루프에 일부 기능을 등록 할 수 있으며 조건이 충족되면 해당 처리 메서드가 호출됩니다.
  • coroutine: 중국어 번역은 코 루틴이라고 불리며 파이썬에서는 코 루틴 객체 유형이라고도하며 시간 루프에 코 루틴 객체를 등록 할 수 있으며 이벤트 루프에 의해 호출됩니다. async 키워드를 사용하여 메서드를 정의 할 수 있습니다.이 메서드는 호출 될 때 즉시 실행되지 않지만 코 루틴 객체를 반환합니다.
  • task: 태스크, 태스크의 다양한 상태를 포함하는 코 루틴 객체의 추가 캡슐화입니다.
  • future: 향후 수행 될 작업의 결과를 나타내며 실제로 작업과 본질적인 차이는 없습니다.

위의 지식 기반에서 코드를 누를 수 있습니다.

 

import asyncio
import uuid


user_agent='Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0'

def parse_page(host,res):
    print('%s 解析结果 %s' %(host,len(res)))
    with open('%s.html' %(uuid.uuid1()),'wb') as f:
        f.write(res)

async def get_page(host,port=80,url='/',callback=parse_page,ssl=False,encode_set='utf-8'):
    print('下载 http://%s:%s%s' %(host,port,url))

    
    if ssl:
        port=443
    #发起tcp连接, IO阻塞操作
    recv,send=await asyncio.open_connection(host=host,port=port,ssl=ssl) 

    #封装http协议的报头,因为asyncio模块只能封装并发送tcp包,因此这一步需要我们自己封装http协议的包
    request_headers="""GET {} HTTP/1.0\r\nHost: {}\r\nUser-agent: %s\r\n\r\n""".format(url,host,user_agent) 

    request_headers=request_headers.encode(encode_set)

    #发送构造好的http请求(request),IO阻塞
    send.write(request_headers)
    await send.drain()

    #接收响应头 IO阻塞操作
    while True:
        line=await recv.readline()
        if line == b'\r\n':
            break
        print('%s Response headers:%s' %(host,line))

    #接收响应体 IO阻塞操作
    text=await recv.read()

    #执行回调函数
    callback(host,text)

    #关闭套接字
    send.close() #没有recv.close()方法,因为是四次挥手断链接,双向链接的两端,一端发完数据后执行send.close()另外一端就被动地断开


if __name__ == '__main__':
    tasks=[
        get_page('www.gov.cn',url='/',ssl=False),
        get_page('www.douyu.com',url='/',ssl=True),
    ]

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

async / await 키워드를 사용하면 간결하고 이해하기 쉬울까요?

HTTP (S) 헤더를 직접 캡슐화하는 것은 정말 번거롭기 때문에 다음 단계는 이미 캡슐화 된이 섹션의 마스터 인 aiohttp에게 문의하는 것입니다.
보충 :asyncio 단일 스레드 동시 IO 작업을 실현할 수 있습니다. 클라이언트 측에서만 사용하면 많은 힘을 발휘하지 못합니다. asyncio웹 서버와 같은 서버 측에서 사용하는 경우 HTTP 연결이 IO 작업이므로 단일 스레드 +를 사용하여 coroutine여러 사용자에 대한 높은 동시 지원 달성 할 수 있습니다 . asyncio목적을 달성하기 위해 TCP, UDP, SSL등의 계약을, aiohttp그것을 기반으로 asyncioHTTP 프레임 워크의 구현입니다. 두 부분으로 나뉩니다. 한 부분은 Client(크롤러가 클라이언트 작업을 시뮬레이션하기 때문에 사용할 부분) Server이고 다른 부분은 , 자세한 내용은 공식 문서를 참조 할 수 있습니다 .

아래에서는 aiohttp를 사용하여 위 코드를 다시 작성합니다.

 

import asyncio
import uuid
import aiohttp


user_agent='Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0'

def parse_page(url,res):
    print('{} 解析结果 {}'.format(url,len(res)))
    with open('{}.html'.format(uuid.uuid1()),'wb') as f:
        f.write(res)


async def get_page(url,callback=parse_page):
    session = aiohttp.ClientSession()
    response = await session.get(url)
    if response.reason == 'OK':
        result = await response.read()
    session.close()
    callback(url,result)
    

if __name__ == '__main__':
    tasks=[
        get_page('http://www.gov.cn'),
        get_page('https://www.douyu.com'),
    ]

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

더 간결합니까?


꼬인

twisted그것은 웹 프레임 워크이며, 파이썬 크롤러를 처음 접하는 사람들도 글을 Scrapy기반으로 하는 크롤러 프레임 워크입니다 twisted. 그 기능 중 하나는 비동기 요청을 보내고 IO를 감지하고 자동으로 전환하는 것입니다.
다음과 같이 twisted를 기반으로 위의 코드를 수정하십시오.

 

from twisted.web.client import getPage,defer
from twisted.internet import reactor
import uuid


def tasks_done(arg):
    reactor.stop() #停止reactor


#定义回调函数
def parse_page(res):
    print('解析结果 {}'.format(len(res)))
    with open('{}.html'.format(uuid.uuid1()),'wb') as f:
        f.write(res)

defer_list=[]#初始化一个列表来存放getPage返回的defer对象

urls=[

    'http://www.gov.cn',
    'https://www.douyu.com',
]

for url in urls:
    obj = getPage(url.encode('utf-8'),) #getPage会返回一个defer对象
    obj.addCallback(parse_page) #给defer对象添加回调函数
    defer_list.append(obj) #将defer对象添加到列表中

defer.DeferredList(defer_list).addBoth(tasks_done) #任务列表结束后停止reactor.stop

reactor.run #启动监听

이것은 단순한 응용 프로그램 일 뿐이며 나중에 상황에 따라 Twisted 마무리 기사를 쓸 수 있습니다.



저자 : 연을 날리는 프랭클린
링크 : https : //www.jianshu.com/p/aa93b7ae2a56
출처 : Jane 책
의 저작권은 저자에게 있습니다. 상업적 재판의 경우 저자에게 연락하여 허가를 받고 비상업적 재판의 경우 출처를 표시하십시오.

추천

출처blog.csdn.net/smilejiasmile/article/details/109523405