Scrapy框架
Scrapy介绍
#1 通用的网络爬虫框架,爬虫界的django #2 scrapy执行流程 5大组件 -引擎(EGINE):大总管,负责控制数据的流向 -调度器(SCHEDULER):由它来决定下一个要抓取的网址是什么,去除重复的网址(集合,布隆过滤器) -深度优先 -摁着一条线爬取 -先进先出 -广度优先 -后进先出 -下载器(DOWLOADER):用于下载网页内容, 并将网页内容返回给EGINE,下载器是建立在twisted这个高效的异步模型上的 -爬虫(SPIDERS):开发人员自定义的类,用来解析responses,并且提取items,或者发送新的请求request -项目管道(ITEM PIPLINES):在items被提取后负责处理它们,主要包括清理、验证、持久化(比如存到数据库)等操作 2大中间件 -爬虫中间件:位于EGINE和SPIDERS之间,主要工作是处理SPIDERS的输入和输出(用的很少) -下载中间件:引擎和下载器之间,加代理,加头,集成selenium # 3 开发者只需要在固定的位置写固定的代码即可(写的最多的spider)
Scrapy安装
#1 pip3 install scrapy(mac,linux) #2 windows上(80%能成功,少部分人成功不了) 1、pip3 install wheel #安装后,便支持通过wheel文件安装软件,wheel文件官网:https://www.lfd.uci.edu/~gohlke/pythonlibs 3、pip3 install lxml 4、pip3 install pyopenssl 5、下载并安装pywin32:https://sourceforge.net/projects/pywin32/files/pywin32/ 6、下载twisted的wheel文件:http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted 7、执行pip3 install 下载目录\Twisted-17.9.0-cp36-cp36m-win_amd64.whl 8、pip3 install scrapy # 3 就有scrapy命令 -D:\Python36\Scripts\scrapy.exe 用于创建项目
Scrapy创建项目,创建爬虫,运行爬虫
1 scrapy startproject 项目名 -scrapy startproject firstscrapy 2 创建爬虫 -scrapy genspider example example.com -scrapy genspider 爬虫名 爬虫地址 -scrapy genspider chouti dig.chouti.com -一执行就会在spider文件夹下创建出一个py文件,名字叫chouti 3 运行爬虫 -scrapy crawl chouti # 带运行日志 -scrapy crawl chouti --nolog # 不带日志 4 支持右键执行爬虫 -在项目路径下新建一个main.py from scrapy.cmdline import execute execute(['scrapy','crawl','chouti','--nolog'])
Scrapy目录介绍
# 目录介绍 firstscrapy # 项目名字 firstscrapy # 包 -spiders # 所有的爬虫文件放在里面 -baidu.py # 一个个的爬虫(以后基本上都在这写东西) -chouti.py -middlewares.py # 中间件(爬虫,下载中间件都写在这) -pipelines.py # 持久化相关写在这(items.py中类的对象) -main.py # 自己加的,执行爬虫 -items.py # 一个一个的类, -settings.py # 配置文件 scrapy.cfg # 上线相关
settings介绍
1 默认情况,scrapy会去遵循爬虫协议 2 修改配置文件参数,强行爬取,不遵循协议 -ROBOTSTXT_OBEY = False 3 USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36' 4 LOG_LEVEL='ERROR'
爬取抽屉新闻

import scrapy from bs4 import BeautifulSoup from scrapy.http.request import Request from firstscrapy.items import ChoutiItem class ChoutiSpider(scrapy.Spider): name = 'chouti' allowed_domains = ['dig.chouti.com'] start_urls = ['http://dig.chouti.com/'] # # # 使用第三方解析 # # def parse(self, response): # # # print(response.text) # # # 解析数据(第一种方案,自己解析,bs4,lxml) # # soup = BeautifulSoup(response.text,'lxml') # # divs = soup.find_all(class_='link-title') # # for div in divs: # # print(div.text) # # # 想继续爬取其他网址 # # def parse(self, response): # # # 以后解析都在这里 # # print(response.status) # # # 假设解析出一个网址(继续爬取) # # return Request('https://www.baidi.com/',dont_filter=True) # # # 使用自带解析 # # xpath # # xpath:response.xpath('xpath语法').extract() 取所有 # # xpath:response.xpath('xpath语法').extract_first() 取一个 # # # css # # response.css('.link-title::text').extract() # 取文本 # # response.css('.link-title::attr(href)').extract()[0] # 取属性 # def parse(self, response): # # 只有css和xpath # # title_list = response.css('.link-title') # # print(len(title_list)) # # title_list = response.xpath('//a[contains(@class,"link-title")]/text()').extract() # # title_list = response.xpath('//a[contains(@class,"link-title")]').extract() # # print(len(title_list)) # # print(title_list) # # # 解析出所有的标题和图片地址 # div_list = response.xpath('//div[contains(@class,"link-item")]') # for div in div_list: # # extract() 取出列表 即便有一个也是列表 # # extract_first() 取出第一个值 # # title = div.css('.link-title::text').extract() # # title = div.css('.link-title::text').extract_first() # url = div.css('.link-title::attr(href)').extract()[0] # # print(title) # print(url) # 持久化方案一 # def parse(self, response, **kwargs): # ll = [] # div_list = response.xpath('//div[contains(@class,"link-item")]') # for div in div_list: # title = div.css('.link-title::text').extract() # url = div.css('.link-title::attr(href)').extract_first() # phone_url = div.css('.image-scale::attr(src)').extract_first() # # 持久化:方案一(用得少)parser必须返回列表套字典的形式 # ll.append({'title': title, 'url': url, 'phone_url': phone_url}) # return ll # 持久化方案二 def parse(self, response): div_list = response.xpath('//div[contains(@class,"link-item")]') for div in div_list: item = ChoutiItem() title = div.css('.link-title::text').extract_first() url = div.css('.link-title::attr(href)').extract_first() photo_url = div.css('.image-scale::attr(src)').extract_first() if not photo_url: photo_url = '' # item.title=title # item.url=url # item.photo_url=photo_url item['title'] = title item['url'] = url item['photo_url'] = photo_url yield item
Scrapy的数据解析
#xpath: -response.xpath('//a[contains(@class,"link-title")]/text()').extract() # 取文本 -response.xpath('//a[contains(@class,"link-title")]/@href').extract() #取属性 #css -response.css('.link-title::text').extract() # 取文本 -response.css('.link-title::attr(href)').extract_first() # 取属性
Scrapy的持久化存储
#1 方案一:parser函数必须返回列表套字典的形式(了解) scrapy crawl chouti -o chouti.csv #2 方案二:高级,pipline item存储(mysql,redis,文件) -在Items.py中写一个类 -在spinder中导入,实例化,把数据放进去 item['title']=title item['url']=url item['photo_url']=photo_url yield item -在setting中配置(数字越小,级别越高) ITEM_PIPELINES = { 'firstscrapy.pipelines.ChoutiFilePipeline': 300, } -在pipelines.py中写ChoutiFilePipeline -open_spider(开始的时候) -close_spider(结束的时候) -process_item(在这持久化)
自动给抽屉点赞
from selenium import webdriver import time import requests bro=webdriver.Chrome(executable_path='./chromedriver.exe') bro.implicitly_wait(10) bro.get('https://dig.chouti.com/') login_b=bro.find_element_by_id('login_btn') print(login_b) login_b.click() username=bro.find_element_by_name('phone') username.send_keys('18953675221') password=bro.find_element_by_name('password') password.send_keys('lqz123') button=bro.find_element_by_css_selector('button.login-btn') button.click() # 可能有验证码,手动操作一下 time.sleep(10) my_cookie=bro.get_cookies() # 列表 print(my_cookie) bro.close() # 这个cookie不是一个字典,不能直接给requests使用,需要转一下 cookie={} for item in my_cookie: cookie[item['name']]=item['value'] headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36', 'Referer': 'https://dig.chouti.com/'} # ret = requests.get('https://dig.chouti.com/',headers=headers) # print(ret.text) ret=requests.get('https://dig.chouti.com/top/24hr?_=1596677637670',headers=headers) print(ret.json()) ll=[] for item in ret.json()['data']: ll.append(item['id']) print(ll) for id in ll: ret=requests.post(' https://dig.chouti.com/link/vote',headers=headers,cookies=cookie,data={'linkId':id}) print(ret.text) 'https://dig.chouti.com/comments/create' ''' content: 说的号 linkId: 29829529 parentId: 0 '''
全站爬取cnblogs

import scrapy from cnblogs.items import CnblogsItem from scrapy import Request from selenium import webdriver from scrapy.dupefilters import RFPDupeFilter class CnblogSpider(scrapy.Spider): name = 'cnblog' allowed_domains = ['www.cnblogs.com'] start_urls = ['https://www.cnblogs.com/'] bro = webdriver.Chrome(executable_path='../../chromedriver.exe') ''' 爬取原则:scrapy默认是先进先出 -深度优先:详情页先爬 队列:先进去先出来 -广度优先:每一页先爬 栈:先进后出 ''' def parse(self, response): # print(response.text) div_list = response.css('article.post-item') for div in div_list: item = CnblogsItem() title = div.xpath('.//div[1]/a/text()').extract_first() item['title'] = title url = div.xpath('.//div[1]/a/@href').extract_first() item['url'] = url desc = div.xpath('string(.//div[1]/p)').extract_first().strip() item['desc'] = desc # print(title) # print(url) # print(desc) # 继续爬取详情 # callback如果不写默认回调到parse方法 # 如果写了,响应回来的对象就会调到自己写的解析方法中 # 请求和响应之间传递参数,使用meta yield Request(url, callback=self.parser_detail, meta={'item': item}) # 解析出下页的地址 next = 'https://www.cnblogs.com' + response.css('#paging_block>div a:last-child::attr(href)').extract_first() # print(next) yield Request(next) def parser_detail(self, response): content = response.css('#cnblogs_post_body').extract_first() print(str(content)) # item哪里来 item = response.meta.get('item') item['content'] = content yield item
Scrapy的请求传参
# 把要传递的数据放到meta中 yield Request(urlmeta={'item':item}) # 在response对象中取出来 item=response.meta.get('item')
提升scrapy爬取数据的效率
- 在配置文件中进行相关的配置即可:(默认还有一套setting) #1 增加并发: IO密集型 默认scrapy开启的并发线程为32个,可以适当进行增加。在settings配置文件中修改CONCURRENT_REQUESTS = 100值为100,并发设置成了为100。 #2 降低日志级别: 在运行scrapy时,会有大量日志信息的输出,为了减少CPU的使用率。可以设置log输出信息为INFO或者ERROR即可。在配置文件中编写:LOG_LEVEL = ‘INFO’ # 3 禁止cookie: 如果不是真的需要cookie,则在scrapy爬取数据时可以禁止cookie从而减少CPU的使用率,提升爬取效率。在配置文件中编写:COOKIES_ENABLED = False # 4禁止重试: 对失败的HTTP进行重新请求(重试)会减慢爬取速度,因此可以禁止重试。在配置文件中编写:RETRY_ENABLED = False # 5 减少下载超时: 如果对一个非常慢的链接进行爬取,减少下载超时可以能让卡住的链接快速被放弃,从而提升效率。在配置文件中进行编写:DOWNLOAD_TIMEOUT = 10 超时时间为10s
scrapy的中间件(下载中间件)
# 1 都写在middlewares.py # 2 爬虫中间件 # 3 下载中间件 # 4 要生效,一定要配置,配置文件 # 下载中间件 -process_request:返回不同的对象,后续处理不同(加代理...) # 1 更换请求头 # print(type(request.headers)) # print(request.headers) # # from scrapy.http.headers import Headers # request.headers['User-Agent']='' # 2 加cookie ---cookie池 # 假设你你已经搭建好cookie 池了, # print('00000--',request.cookies) # request.cookies={'username':'asdfasdf'} # 3 加代理 # print(request.meta) # request.meta['download_timeout'] = 20 # request.meta["proxy"] = 'http://27.188.62.3:8060' -process_response:返回不同的对象,后续处理不同 - process_exception def process_exception(self, request, exception, spider): print('xxxx') # 不允许直接改url # request.url='https://www.baidu.com' from scrapy import Request request=Request(url='https://www.baidu.com',callback=spider.parser) return request
selenium在scrapy中的使用流程
# 当前爬虫用的selenium是同一个 # 1 在爬虫中初始化webdriver对象 from selenium import webdriver class CnblogSpider(scrapy.Spider): name = 'cnblog' ... bro=webdriver.Chrome(executable_path='../chromedriver.exe') # 2 在中间件中使用(process_request) spider.bro.get('https://dig.chouti.com/') response=HtmlResponse(url='https://dig.chouti.com/',body=spider.bro.page_source.encode('utf-8'),request=request) return response # 3 在爬虫中关闭 def close(self, reason): print("我结束了") self.bro.close()
去重规则源码分析
from scrapy.dupefilters import RFPDupeFilter # 详见代码
布隆过滤器
详情参见:https://www.cnblogs.com/xiaoyuanqujing/protected/articles/11969224.html 极小内存校验是否重复 #python3.6 安装 #需要先安装bitarray pip3 install bitarray-0.8.1-cp36-cp36m-win_amd64.whl(pybloom_live依赖这个包,需要先安装) #下载地址:https://www.lfd.uci.edu/~gohlke/pythonlibs/ pip3 install pybloom_live
分布式爬虫(scrapy-redis)
# 1 pip3 install scrapy-redis # 2 原来继承Spider,现在继承RedisSpider # 3 不能写start_urls = ['https:/www.cnblogs.com/'] # 4 需要写redis_key = 'myspider:start_urls' # 5 setting中配置: # redis的连接 REDIS_HOST = 'localhost' # 主机名 REDIS_PORT = 6379 # 端口 # 使用scrapy-redis的去重 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 使用scrapy-redis的Scheduler # 分布式爬虫的配置 SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 持久化的可以配置,也可以不配置 ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 299 } # 9现在要让爬虫运行起来,需要去redis中以myspider:start_urls为key,插入一个起始地址lpush myspider:start_urls https://www.cnblogs.com/
破解知乎登录(js逆向和解密)
client_id=c3cef7c66a1843f8b3a9e6a1e3160e20& grant_type=password& timestamp=1596702006088& source=com.zhihu.web& signature=eac4a6c461f9edf86ef33ef950c7b6aa426dbb39& username=%2B86liuqingzheng& password=1111111& captcha=& lang=en& utm_source=& ref_source=other_https%3A%2F%2Fwww.zhihu.com%2Fsignin%3Fnext%3D%252F" # 破解知乎登陆 import requests #请求解析库 import base64 #base64解密加密库 from PIL import Image #图片处理库 import hmac #加密库 from hashlib import sha1 #加密库 import time from urllib.parse import urlencode #url编码库 import execjs #python调用node.js from http import cookiejar as cookielib class Spider(): def __init__(self): self.session = requests.session() self.session.cookies = cookielib.LWPCookieJar() #使cookie可以调用save和load方法 self.login_page_url = 'https://www.zhihu.com/signin?next=%2F' self.login_api = 'https://www.zhihu.com/api/v3/oauth/sign_in' self.captcha_api = 'https://www.zhihu.com/api/v3/oauth/captcha?lang=en' self.headers = { 'user-agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36 LBBROWSER', } self.captcha ='' #存验证码 self.signature = '' #存签名 # 首次请求获取cookie def get_base_cookie(self): self.session.get(url=self.login_page_url, headers=self.headers) def deal_captcha(self): r = self.session.get(url=self.captcha_api, headers=self.headers) r = r.json() if r.get('show_captcha'): while True: r = self.session.put(url=self.captcha_api, headers=self.headers) img_base64 = r.json().get('img_base64') with open('captcha.png', 'wb') as f: f.write(base64.b64decode(img_base64)) captcha_img = Image.open('captcha.png') captcha_img.show() self.captcha = input('输入验证码:') r = self.session.post(url=self.captcha_api, data={'input_text': self.captcha}, headers=self.headers) if r.json().get('success'): break def get_signature(self): # 生成加密签名 a = hmac.new(b'd1b964811afb40118a12068ff74a12f4', digestmod=sha1) a.update(b'password') a.update(b'c3cef7c66a1843f8b3a9e6a1e3160e20') a.update(b'com.zhihu.web') a.update(str(int(time.time() * 1000)).encode('utf-8')) self.signature = a.hexdigest() def post_login_data(self): data = { 'client_id': 'c3cef7c66a1843f8b3a9e6a1e3160e20', 'grant_type': 'password', 'timestamp': str(int(time.time() * 1000)), 'source': 'com.zhihu.web', 'signature': self.signature, 'username': '+8618953675221', 'password': '', 'captcha': self.captcha, 'lang': 'en', 'utm_source': '', 'ref_source': 'other_https://www.zhihu.com/signin?next=%2F', } headers = { 'x-zse-83': '3_2.0', 'content-type': 'application/x-www-form-urlencoded', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36 LBBROWSER', } data = urlencode(data) with open('zhih.js', 'rt', encoding='utf-8') as f: js = execjs.compile(f.read(), cwd='node_modules') data = js.call('b', data) r = self.session.post(url=self.login_api, headers=headers, data=data) print(r.text) if r.status_code == 201: self.session.cookies.save('mycookie') print('登录成功') else: print('登录失败') def login(self): self.get_base_cookie() self.deal_captcha() self.get_signature() self.post_login_data() if __name__ == '__main__': zhihu_spider = Spider() zhihu_spider.login()
爬虫的反扒措施
1 user-agent 2 referer 3 cookie(cookie池,先访问一次) 4 频率限制(代理池,延迟) 5 js加密(扣出来,exjs模块执行) 6 css加密 7 验证码(打码平台),半手动 8 图片懒加载