路飞学城—Python—爬虫实战密训班 第三章

今日内容:
1.高性能相关
2.scrapy框架


内容详细:
1. 高性能爬虫相关:
- 提高并发方案 :多进程->多线程->单线程

- 本质:
sk = socket.socket()
# 连接的过程是 阻塞
sk.connect(('www.cnblogs.com',80))
sk.sendall(b'GET /wupeiqi http1.1\r\n......\r\n\r\n')
sk.sendall(b'POST /wupeiqi http1.1\r\n......\r\n\r\nuser=leo&pwd=123')

# 阻塞
data = sk.recv(8096)

sk.cloes()



- 阻塞

***** 第一次连接 www.baidu.com 成功后 发送请求,接收请求,关闭连接 再开始第二次访问www.cnblogs.com
***** 如果有一个线程一直处理连接,成功之后就返回 谁先返回就执行谁 比如三个url依次进入 connect 处于阻塞 谁先回来就谁往下走,
***** 比如cnblogs先回来了 往下走 但是到recv遇见阻塞了,这个时候baidu回来了 再让他往下走 相当于一个线程遇到阻塞 就去干其他的事
***** 1、socket非阻塞 2、监听其他socket的状态

import socket
def task(url):
sk = socket.socket()
# 连接的过程是 阻塞
sk.connect((url,80))
# HTTP协议 请求头和请求体之间\r\n\r\n分割 基于TCP协议
connect = 'GET /wupeiqi HTTP/1.1\r\nHost: %s\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36\r\n\r\n' %url
# 发送字节 字符串转字节
sk.sendall(connect.encode('utf-8'))

# 等待服务端返回内容是阻塞的

data = sk.recv(8096)
print(data)
sk.close()


url_list = [
'www.baidu.com',
'www.cnblogs.com',
'www.jiandan.com',
]

for url in url_list:
# 串行
import socket


IO多路复用:监听多个socket是否发生变化

- 问题:
- 非阻塞
- 不等待 (报错,捕捉异常)
- sk = socket.socket()
# 写上这个就不会阻塞
sk.setblocking(False)

- 监听socket变化

什么是异步非阻塞:
- 非阻塞
- 不等待 (报错,捕捉异常)
- sk = socket.socket()
# 写上这个就不会阻塞
sk.setblocking(False)
- 异步:
- 回调,当达到某个指定的状态之后,自动调用特定函数

- 示例:nb_async.py 实现异步非阻塞模块

*** 内容梳理:
a.提高并发方案:
- 多进程,多线程,利用'异步非阻塞'模块实现单线程并发请求

b.异步非阻塞
- 异步:回调,把一个函数交给某个功能,完成后自动再执行一下这个函数
- 非阻塞:体现的就是不等待,当遇到IO请求的时候,直接获取,不等待是否已经完成,相当于执行过去,如果有报错,直接捕捉异常 sk.setblocking(False)

c.如何自定义异步非阻塞模块?
- 基于socket设置setblocking和IO多路复用来实现
- 爬虫发送Http请求本质,创建socket对象;
- IO多路复用"循环"监听socket是否发生变化,一旦发生变化,我们可以自定义操作(触发某个函数的执行)

什么是协程?
协程是"微线程",让一个线程先执行某几行代码,再跳到某处 再执行某几行代码。

通过yield实现一个协程:

def func1():
print(’adsfadsf‘)
print(’adsfadsf‘)
print(’adsfadsf‘)
yield 1
print(’adsfadsf‘)
print(’adsfadsf‘)
print(’adsfadsf‘)
yield 2
yield 3
yield 4

def func2():
print(’adsfadsf‘)
print(’adsfadsf‘)
print(’adsfadsf‘)
yield 11
yield 12
yield 19

g1 = func1()
g2 = func2()

g1.send(None)
g1.send(None)

g2.send(None)

通过greenlet模块: http://www.cnblogs.com/wupeiqi/articles/5040827.html



from greenlet import greenlet

def test1():
print 12
gr2.switch()
print 34
gr2.switch()


def test2():
print 56
gr1.switch()
print 78

gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()

如果协程+遇到IO就切换 = 》



原理:
1、什么是协程?
-协程是“微线程”,他本身是不存在的,是程序员认为创造出来并控制程序先执行某段代码 再跳到某处执行某段代码,
如果遇到非IO请求来回切换,性能就更低
如果遇到IO请求就切换的情况,性能会提高,实现并发(本质上利用IO等待的过程,再去干其他事情)

2.Http请求的本质?

3.IO多路复用的作用?
- select 内存循环检测socket是否发生变化,监听最多的socket个数1024
- poll 内存循环检测socket是否发生变化,没有限制
- epoll 回调的方式,没有个数的限制

4、异步非阻塞?
- 异步:回调
- 非阻塞:不等待

5、自定义异步非阻塞模块
- 基于事件循环
- 基于协程
- 本质:socket + IO多路复用

使用:
情况一、
import asyncio
import requests


@asyncio.coroutine
def fetch_async(func, *args):
loop = asyncio.get_event_loop()
future = loop.run_in_executor(None, func, *args)
response = yield from future
print(response.url, len(response.content))


tasks = [
fetch_async(requests.get, 'http://www.cnblogs.com/wupeiqi/'),
fetch_async(requests.get, 'http://dig.chouti.com/pic/show?nid=4073644713430508&lid=10273091')
]

loop = asyncio.get_event_loop()
results = loop.run_until_complete(asyncio.gather(*tasks))
loop.close()

情况二、
import gevent
from gevent import monkey
monkey.patch_all()
import requests


def fetch_async(method, url, req_kwargs):
print(method, url, req_kwargs)
response = requests.request(method=method, url=url, **req_kwargs)
print(response.url, len(response.content))

# ##### 发送请求 #####
gevent.joinall([
gevent.spawn(fetch_async, method='get', url='https://www.cnblogs.com/', req_kwargs={}),
gevent.spawn(fetch_async, method='get', url='https://www.baidu.com/', req_kwargs={}),
gevent.spawn(fetch_async, method='get', url='https://www.sogo.com/', req_kwargs={}),
])

情况三、
from twisted.web.client import getPage, defer
from twisted.internet import reactor


def all_done(arg):
reactor.stop()


def callback(contents):
print(contents)


deferred_list = []

url_list = ['http://www.bing.com', 'http://www.baidu.com', ]
for url in url_list:
deferred = getPage(bytes(url, encoding='utf8'))
deferred.addCallback(callback)
deferred_list.append(deferred)

dlist = defer.DeferredList(deferred_list)
dlist.addBoth(all_done)

reactor.run()


安装scarpy
scarypy模块是什么?
- 帮我们提供一个可扩展功能齐全的爬虫框架。

安装:
linux/mac pip3 install scarpy

windows:现在也直接 pip3 install scrapy

a pip3 install wheel
b 下载twsited https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
c 进入下载目录:执行 pip3 install Twisted-xxxxx.whl

安装:scrapy
d pip3 install scrapy -i http://pypi.douban.com/simple --trusted-host pypi.douban.com # -i 指定douban的pip源
安装 pywin32
e pip3 install pywin32 -i http://pypi.douban.com/simple --trusted-host pypi.douban.com

安装scarpy 出现 error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": http://landinghub.visualstudio.com/visual-cpp-build
解决;Microsoft .NET Framework 要升值到4.5.1以上 再安装 visualcppbuildtools full(MS Visual C++ 14报错解决) Microsoft .NET Framework 要升值到4.5.1以上

# 如果出现警告则更新组件
D:\django\PySpider\Scrapy框架\touch>scrapy genspider chouti chouti.com
:0: UserWarning: You do not have a working installation of the service_identity module: 'cannot import name 'opentype''. Please install it from
<http
s://pypi.python.org/pypi/service_identity> and make sure all of its dependencies are satisfied. Without the service_identity module, Twisted can perf
orm only rudimentary TLS client hostname verification. Many valid certificate/hostname mappings may be rejected.


https://blog.csdn.net/weixin_41917563/article/details/79978819
http://www.mamicode.com/info-detail-2162790.html

pip3 install service_identity-17.0.0-py2.py3-none-any.whl

pip3 install service_identity --force --upgrade

pip3 install pywin32 -i http://pypi.douban.com/simple --trusted-host pypi.douban.com


快速使用:
Django:
django-admin startproject mysite
cd mysite
python manage.py startapp app01
# 写代码
python manage.py runserver

Scrapy:
安装在Python的安装目录下 C:\Python36\Scripts\scrapy.exe # django-admin也在

cmd —> C:\Users\cc>D: D:\>cd django\PySpider\Scrapy框架 dir查看

- 创建项目:
D:\django\PySpider\Scrapy框架>scrapy startproject touch

项目目录:
D:\django\PySpider\Scrapy框架\touch

创建chouti的爬虫 和 博客园 chouti.py就和django的app是一样的,每一个文件就是一个爬虫
D:\django\PySpider\Scrapy框架\touch>scrapy genspider chouti chouti.com

D:\django\PySpider\Scrapy框架\touch>scrapy genspider cnblogs cnblogs.com

#Created spider 'cnblogs' using template 'basic' in module:
#touch.spiders.cnblogs

创建命令总结:
- 创建项目
scrapy startproject touch #(touch是项目名)
- 进入目录 今后创建的目录不应该有中文
cd D:\django\PySpider\MyScrapy\touch\touch
- 创建爬虫 爬取网站名命名
scrapy genspider chouti chouti.com

# -*- coding: utf-8 -*-
import scrapy
# import sys,os
# windows打印编码错误解决
# sys.stdout=io.TextIOWrapper(sys.stdout.buffer,encoding='gb18030')

class ChoutiSpider(scrapy.Spider):
name = 'chouti'
allowed_domains = ['chouti.com'] # 允许的域名找,定向只爬这一个网站
start_urls = ['http://chouti.com/'] # 入口

def parse(self, response): # 回调函数 他会去下载起始的URL 然后调用parse函数 response就是这个页面的结果的对象
pass

scrapy crawl chouti --nolog # 内部使用: twsited 具有异步+非阻塞 一个线程爬取很多页面
<200 https://dig.chouti.com/>

*** Scrapy知识总结:
1、spider 编写爬虫程序,去解析并处理请求。
def parser():
- HtmlXPathSelector
- yield item 会把item交给 pipelines ,pipelines需要在settigs里注册
- yield Request 交给调度器再执行

2、item/pipelines
配置:
ITEM_PIPELINES = {
'touch.pipelines.TouchPipeline': 300,
}

使用:应该有5个方法
class TouchPipeline(object):
def process_item(self, item, spider):
# print(item['href'])
self.f.write(item['href']+'\n')
self.f.flush() # 强制刷新到硬盘
return item


def open_spider(self, spider):
"""
爬虫开始执行时,调用
:param spider:
:return:
"""
# print('要开始了')
self.f = open('url.log','w') # 也可以追加模式


def close_spider(self, spider):
"""
爬虫关闭时,被调用
:param spider:
:return:
"""
# print('完蛋了 ')
self.f.close()

猜你喜欢

转载自www.cnblogs.com/touchlixiang/p/9302452.html
今日推荐