内容回顾
- 模拟登陆:
- sometimes我们需要爬取基于当前用户的用户信息(需要登录后才可查看)
- 实现流程:
- 借助于抓包工具,抓取点击登录按钮发起的post请求(url,参数(动态参数))
- 携带cookie对其他子页面进行请求发送
- 反爬机制:
- robots
- UA检测
- 验证码
- cookie
- 禁ip
- 动态请求参数
在程序中 是否可以一味的使用多线程,多进程?
(推荐)使用单线程+多任务异步协程
event_loop:事件循环,相当于一个无限循环,我们可以把一些特殊函数注册(放置)到这个事件循环上,当满足某些条件的时候,函数就会被循环执行。
程序是按照设定的顺序从头执行到尾,运行的次数也是完全按照设定。当在编写异步程序时,必然其中有部分程序的运行耗时是比较久的,需要先让出当前程序的控制权,让其在背后运行,让另一部分的程序先运行起来。
当背后运行的程序完成后,也需要及时通知主程序已经完成任务可以进行下一步操作,但这个过程所需的时间是不确定的,需要主程序不断的监听状态,一旦收到了任务完成的消息,就开始进行下一步。loop就是这个持续不断的监视器。
coroutine:中文翻译叫协程,在 Python 中常指代为协程对象类型,我们可以将协程对象注册到事件循环中,
它会被事件循环调用。我们可以使用 async 关键字来定义一个方法,这个方法在调用时不会立即被执行,
而是返回一个协程对象。
task:任务,它是对协程对象的进一步封装,包含了任务的各个状态。
future:代表将来执行或还没有执行的任务,实际上和 task 没有本质区别。
另外我们还需要了解 async/await 关键字,它是从 Python 3.6 才出现的,专门用于定义协程。其中,async 定义一个协程,await 用来挂起阻塞方法的执行。
了解携程概念基础后,我们来看看一个协程,具体需要什么?
事件循环-就是一个无限的循环
协程:特殊的函数 async修饰之后 这个函数调用不会立刻执行 会封转到协程对象,只有协程注册到任务对象中,并且任务对象注册事件循环中 才会执行
await:只要有阻塞操作 一定要在前面加await
协程基础
import asyncio async def request(url): print('正在请求',url) print('下载成功',url) c=request('www.baidu.com') #实例化一个事件对象 loop=asyncio.get_event_loop()
#创建一个任务对象 将协程封装到该对象中 #task=loop.create_task(c) #第二种任务对象 task=asyncio.ensure_future(c) #将协程对象注册到事件循环对象中,并启动事件循环对象 loop=run_until_complete(task)
多任务异步协程
from time import sleep import asyncio import time urls = ['www.baidu.com','www.sogou.com','www.goubanjia.com'] start = time.time() async def request(url): print('正在请求:',url) #在多任务异步协程实现中,不可以出现不支持异步的相关代码。 # sleep(2) await asyncio.sleep(2) print('下载成功:',url) #实例化一个事件循环对象 loop = asyncio.get_event_loop() #任务列表:放置多个任务对象 tasks = [] for url in urls: c = request(url) #实例化任务对象的方法 task = asyncio.ensure_future(c) tasks.append(task) #将协程对象注册到事件循环中,并且我们需要启动事件循环 loop.run_until_complete(asyncio.wait(tasks)) print(time.time()-start)
多任务异步协程应用到爬虫中
可以看到 每次访问 都需要2秒 一种是6秒
from flask import Flask import time app = Flask(__name__) @app.route('/bobo') def index_bobo(): time.sleep(2) return 'Hello bobo' @app.route('/jay') def index_jay(): time.sleep(2) return 'Hello jay' @app.route('/tom') def index_tom(): time.sleep(2) return 'Hello tom' if __name__ == '__main__': app.run(threaded=True)
#aiohttp:支持异步的一个基于网络请求的模块 # pip install aiohttp import asyncio import aiohttp import time #单线程+多任务异步协程 urls = [ 'http://127.0.0.1:5000/jay', 'http://127.0.0.1:5000/bobo', 'http://127.0.0.1:5000/tom' ] #代理操作有变化: #async with await s.get(url,proxy="http://ip:port") as response: async def get_pageText(url): async with aiohttp.ClientSession() as s:#实例化一个请求对象 async with await s.get(url) as response:#get方法 page_text=await response.text()#只要有阻塞操作就使用await 获取返回值 #借助回调函数进行响应数据的解析操作 return page_text #封装回调函数用于数据解析 def parse(task): #1.获取响应数据 page_text = task.result() print(page_text+',即将进行数据解析!!!') #解析操作写在该位置 start=time.time() tasks=[] for url in urls: c=get_pageText(url)#使用这个函数 task=asyncio.ensure_future(c)#对协程的一种封装 task.add_done_callback(parse) # 给任务对象绑定回调函数用于数据解析 tasks.append(task) loop=asyncio.get_event_loop()#创建一个事件对象 loop.run_until_complete(asyncio.wait(tasks))#将协程注册到循环中,并启用这个循环 print(time.time()-start)
selenium
概念:是一个基于浏览器自动话的模块.
和爬虫之间的关联?
帮我们便捷的爬取到页面中动态加载出来的数据
实现模拟登陆
使用流程:
pip install selenium
下载对应的驱动程序:http://chromedriver.storage.googleapis.com/index.html
查看对应的版本:https://blog.csdn.net/huilan_same/article/details/51896672
selenium简单应用
from selenium import webdriver from lxml import etree import time bro=webdriver.Chrome(executable_path='./chromedriver.exe')#导入驱动 #让浏览器对指定url发起访问 bro.get('http://125.35.6.84:81/xk/') #获取浏览器当前打开页面源码(可见既可得) page_text=bro.page_source time.sleep(2) tree=etree.HTML(page_text) name = tree.xpath('//*[@id="gzlist"]/li[1]/dl/a/text()')[0] print(name) time.sleep(2) bro.quit()
####################打开淘宝输入想搜索的值###########################
from selenium import webdriver
import time
bro=webdriver.Chrome(executable_path='./chromedriver.exe')
bro.get('https://www.taobao.com')
#节点定位find系列的方法
input_text=bro.find_element_by_id('q')#找到id为q的
#节点交互
input_text.send_keys('苹果')#在text中输入苹果
time.sleep(2)
动作链
from selenium import webdriver #导入动作链对应的模块 from selenium.webdriver import ActionChains import time bro=webdriver.Chrome(executable_path='./chromedriver.exe') bro.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable') #如果定位的节点是被包含在iframes节点之中的,则必须使用switch_to进行frame的切换 bro.switch_to.frame('iframeResult') #iframe div_tag=bro.find_element_by_id('draggable')#获取id #实例化一个动作链对象(需要将浏览器对象作为参数传递给该对象的构造方法) action=ActionChains(bro) #单击且长按 action.click_and_hold(div_tag) for i in range(5): #让div向右移动 action.move_by_offset(17,0).perform()#移动17*5 perform立刻执行动作链 time.sleep(0.5) time.sleep(2) bro.quit()#退出
无头浏览器
from selenium import webdriver from lxml import etree import time from selenium.webdriver.chrome.options import Options chrome_options = Options() chrome_options.add_argument('--headless') chrome_options.add_argument('--disable-gpu') bro = webdriver.Chrome(executable_path='./chromedriver.exe',chrome_options=chrome_options) #让浏览器对指定url发起访问 bro.get('http://125.35.6.84:81/xk/') #获取浏览器当前打开页面的页面源码数据(可见即可得) page_text = bro.page_source time.sleep(2) tree = etree.HTML(page_text) name = tree.xpath('//*[@id="gzlist"]/li[1]/dl/a/text()')[0] print(name) time.sleep(3) bro.quit()
selenium规避被检测
from selenium import webdriver from lxml import etree import time #规避检查 from selenium.webdriver import ChromeOptions option = ChromeOptions() option.add_experimental_option('excludeSwitches', ['enable-automation']) bro = webdriver.Chrome(executable_path='./chromedriver.exe',options=option) #让浏览器对指定url发起访问 bro.get('http://125.35.6.84:81/xk/') #获取浏览器当前打开页面的页面源码数据(可见即可得) page_text =bro.page_source time.sleep(2) tree=etree.HTML(page_text) name = tree.xpath('//*[@id="gzlist"]/li[1]/dl/a/text()')[0] print(name) time.sleep(5) bro.quit()
模拟QQ空间
import request from selenium import webdriver import time driver=webdriver.Chrome(executable_path='./chromedriver.exe') driver.get('https://qzone.qq.com/') # 在web 应用中经常会遇到frame 嵌套页面的应用,使用WebDriver 每次只能在一个页面上识别元素,对于frame 嵌套内的页面上的元素,直接定位是定位是定位不到的。这个时候就需要通过switch_to_frame()方法将当前定位的主体切换了frame 里。 driver.switch_to.frame('login_frame') driver.find_element_by_id('switcher_plogin').click() driver.find_element_by_id('u').send_keys('1820405927') # 这里填写你的QQ号 driver.find_element_by_id('p').send_keys('xxxxxx') # 这里填写你的QQ密码 driver.find_element_by_id('login_button').click() time.sleep(2) driver.close()
pyppeteer
类是于一个centos的selenium 使用谷歌测试版的浏览器
超级鹰自动登陆12306
pip install Pillow
chaojiying_api.py
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 # 898175 self.base_params = { 'user': self.username, 'pass2': self.password, 'softid': self.soft_id, # 898175 } 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()
12306程序
from selenium import webdriver import time from selenium.webdriver import ActionChains from PIL import Image#切图 from chaojiying_api import Chaojiying_Client bro=webdriver.Chrome(executable_path=r'./chromedriver.exe') bro.get('https://kyfw.12306.cn/otn/login/init') time.sleep(2) code_img_ele=bro.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img') time.sleep(2) #验证码图片的左上角的坐标 location=code_img_ele.location#x,y print('验证码图片左上角的坐标',location) size=code_img_ele.size#验证码图片的长和宽 print('验证码图片的长和宽size',size) #验证码左上角和右下角这2个点的坐标 rangle=( int(location['x']), int(location['y']), int(location['x'] + size['width']), int(location['y'] + size['height']) ) #截取当前浏览器打开的这张页面的图像 bro.save_screenshot('aa.png') i=Image.open('./aa.png')#读取这个图片 #截取下来验证码图片名称 code_img_name='./code.png' #crop就可以根据左上角 和右下角的坐标进行指定区域的截取 frame=i.crop(rangle) frame.save(code_img_name) import requests chaojiying = Chaojiying_Client('xuebaohua', '6xbh', '898175') # 用户中心>>软件ID 生成一个替换 96001 im=open('./code.png','rb').read() result=chaojiying.PostPic(im,9004)#9004验证类型 print("***************", result) result=result['pic_str']#取得坐标 all_list = [] #[[x1,y1],[x2,y2],[x3,y3]] if '|' in result: # 117,75|188,146|117,75 list_1 = result.split('|') count_1 = len(list_1) for i in range(count_1): xy_list = [] x = int(list_1[i].split(',')[0]) #[[x,y],[]] y = int(list_1[i].split(',')[1]) xy_list.append(x) xy_list.append(y) all_list.append(xy_list) print(all_list,'11111')#存储的形式 [[260, 62], [46, 139]] 11111 else: x = int(result.split(',')[0]) #[[x,y]] y = int(result.split(',')[1]) xy_list = [] xy_list.append(x) xy_list.append(y) all_list.append(xy_list) print(all_list)#[[251, 76]] code_img = bro.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img') action = ActionChains(bro)#进行动作链 for l in all_list: x=l[0] y=l[1] #拿到截图图片进行过便宜 ActionChains(bro).move_to_element_with_offset(code_img, x, y).click().perform() bro.find_element_by_id('username').send_keys('135022') time.sleep(2) bro.find_element_by_id('password').send_keys('166') time.sleep(2) bro.find_element_by_id('loginSub').click() ''' 验证码图片左上角的坐标 {'x': 278, 'y': 274} 验证码图片的长和宽size {'height': 190, 'width': 293} *************** {'err_no': 0, 'err_str': 'OK', 'pic_id': '3069614092051000215', 'pic_str': '251,76', 'md5': '93d0b89555d109be569a44c5a3b997fe'} [[251, 76]] ''' ''' #2个12306验证码的数据 验证码图片左上角的坐标 {'x': 278, 'y': 274} 验证码图片的长和宽size {'height': 190, 'width': 293} *************** {'err_no': 0, 'err_str': 'OK', 'pic_id': '9069614192051000217', 'pic_str': '260,62|46,139', 'md5': '818ec8e895f95dff73a86583890038b0'} [[260, 62], [46, 139]] 11111 [[260, 62], [46, 139]] '''
scrapy:爬虫框架 异步爬取,高性能的数据解析+持久化存储操作
框架:集成了很多功能且具有很强通用性的一个项目模版
如何学习框架:
学习框架的功能模块的具体使用
环境的安装:
linux:
pip install scrapy
windows:
a. pip3 install wheel b. 下载twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted c. 进入下载目录,执行 pip3 install Twisted‑17.1.0‑cp35‑cp35m‑win_amd64.whl#借助实现异步 d. pip3 install pywin32 e. pip3 install scrapy
scrapy:爬取糗事百科文字
使用流程
创建一个工程:scrapy startproject firstBlood
cd firstBlood
创建爬虫文件: scrapy genspider first www.baiud.com
执行工程:scrapy crawl first --nolog(可选)
命令行的:scrapy crawl qiutu -o qiutu.csv 执行保存(局限性比较强,只能是.csv)
基于管道:
命令行代码
# -*- coding: utf-8 -*- import scrapy #爬虫类 class FirstSpider(scrapy.Spider): #爬虫文件的名称 name = 'qiutu' #允许的域名 # allowed_domains = ['www.baiud.com'] #起始的url列表 被进行自动的请求发送 start_urls = ['https://www.qiushibaike.com/text/'] #用来解析数据 #持久化存储,只可以将parse方法的返回值存储到磁盘文件 def parse(self, response): div_list=response.xpath('//*[@id="content-left"]/div') all_data=[] for div in div_list: # author=div.xpath('./div[1]/a[2]/h2/text()')[0].extract() author = div.xpath('./div[1]/a[2]/h2/text()').extract_first() content=div.xpath('.//div[@class="content"]/span//text()').extract() content=''.join(content)#将列表转成字符串 dic={ 'author':author, 'content':content } all_data.append(dic) return all_data
持久化存储代码
items.py
import scrapy class FirstbloodItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field()#万能的数据类型 author = scrapy.Field() content = scrapy.Field()
first.py
from ..items import FirstbloodItem #爬虫类 class FirstSpider(scrapy.Spider): #爬虫文件的名称 name = 'qiutu' #允许的域名 # allowed_domains = ['www.baiud.com'] #起始的url列表 被进行自动的请求发送 start_urls = ['https://www.qiushibaike.com/text/'] def parse(self, response): div_list=response.xpath('//*[@id="content-left"]/div') all_data=[] for div in div_list: # author=div.xpath('./div[1]/a[2]/h2/text()')[0].extract() author = div.xpath('./div[1]/a[2]/h2/text()').extract_first() # print(author,'11111') content=div.xpath('.//div[@class="content"]/span//text()').extract() content=''.join(content)#将列表转成字符串 #实例化一个item类型的对象 item=FirstbloodItem() #使用中括号的形式访问item对象中的属性 item['author'] = author item['content'] = content #将item内容提交到管道 yield item
pipelines.py
import pymysql from redis import Redis class FirstbloodPipeline(object): fp = None def open_spider(self,spider):#修改父类方法 print('开始爬虫.......') self.fb=open('./qiutu.txt','w',encoding='utf-8') def process_item(self, item, spider): author=item['author']#取属性赋值 content = item['content'] self.fb.write(author+':'+content+' ') return item #返回给了下一个即将被执行的管道类 def close_spider(self,spider): print('结束爬虫!!!') self.fb.close() class FirstbloodPipeline(object): conn=None cursor=None def open_spider(self,spider): self.conn=pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='123456.com',db='qiutu') print(self.conn) def process_item(self,item,spider): self.cursor=self.conn.cursor() try: self.cursor.execute('insert into qiutu values("%s","%s")'%(item['author'],item['content'])) self.conn.commit() except Exception as e: print(e) self.conn.rollback()#回滚 return item def close_spider(self,seider): self.cursor.close() self.conn.close()
scrapy:使用总结
创建工程 scrapy startproject proname
创建爬虫文件
cd proname
scrapy genspider spidername www.baidu.com
爬虫相关属性方法
爬虫文件的名称:name
起始的url列表:start_urls 存储的url会被scrapy进行自动的请求发送
parse(reponse):用来解析start_urls列表中url对应的响应数据
response.xpath() extrct()
数据持久化存储
-基于终端指定
只可以将parse方法的返回值进行持久化存储
scrapy crawl spidername -o ./file
-基础管道持久化存储的编码流程
数据解析
-在item类中声明相关的属性用于存储解析到数据
-将解析到的数据存储封装到item类型对象中
-将item对象提交给管道类
-item会被管道类中的process_item方法的item参数进行接收
-process_item方法中编写基于item持久化存储的操作
-在配置文件中开启管道
管道细节处理:
-管道文件中一个类对应的是什么?
一个类表达式将解析到数据存储到某一个具体的平台中
-process_item方法中的返回值表示什么含义
return item就是说将item传递给下一个即将被执行的管道类
-open_spider#开始 close_spider#结束
手动请求发送
yield scrapy.Request(url,callback):callback回调一个函数用于数据解析
爬取阳光网前5页数据
import scrapy from sunLinePro.items import SunlineproItem class SunSpider(scrapy.Spider): name = 'sun' # allowed_domains = ['www.xxx.com'] start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page='] #通用的url模板(不可以修改) url = 'http://wz.sun0769.com/index.php/question/questionType?type=4&page=%d' page = 1 def parse(self, response): print('--------------------------page=',self.page) tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr') for tr in tr_list: title = tr.xpath('./td[2]/a[2]/text()').extract_first() status = tr.xpath('./td[3]/span/text()').extract_first() item = SunlineproItem() item['title'] = title item['status'] = status yield item if self.page < 5: #手动对指定的url进行请求发送 count = self.page * 30 new_url = format(self.url%count) self.page += 1 # 手动对指定的url进行请求发送 yield scrapy.Request(url=new_url,callback=self.parse)
post请求发送和cookie的处理
- post请求的发送:(麻烦少用,登陆用requests)
- 重写父类的start_requests(self)方法
- 在该方法内部只需要调用yield scrapy.FormRequest(url,callback,formdata)
- cookie处理:scrapy默认情况下会自动进行cookie处理
#COOKIES_ENABLED = False 如果把注释取消就不会存储
import scrapy class PostdemoSpider(scrapy.Spider): name = 'postDemo' # allowed_domains = ['www.xxx.com'] #https://fanyi.baidu.com/sug start_urls = ['https://fanyi.baidu.com/sug'] #父类方法,就是将start_urls中的列表元素进行get请求的发送 # def start_requests(self): # for url in self.start_urls: # yield scrapy.Request(url=url,callback=self.parse) def start_requests(self): for url in self.start_urls: data = { 'kw':'cat' } #post请求的手动发送使用的是FormRequest yield scrapy.FormRequest(url=url,callback=self.parse,formdata=data) def parse(self, response): print(response.text)
请求传参:
- 使用场景:如果使用scrapy爬取的数据没有在同一张页面中,则必须使用请求传参
- 基于起始url进行数据解析(parse) - 解析数据 - 电影的名称 - 详情页的url - 对详情页的url发起手动请求(指定的回调函数parse_detail),进行请求传参(meta) meta传递给parse_detail这个回调函数 - 封装一个其他页码对应url的一个通用的URL模板 - 在for循环外部,手动对其他页的url进行手动请求发送(需要指定回调函数==》parse) - 定义parse_detail回调方法,在其内部对电影的简介进行解析。解析完毕后,需要将解析到的电影名称 和电影的简介封装到同一个item中。 - 接收传递过来的item,并且将解析到的数据存储到item中,将item提交给管道
5大核心组键
spider对url进行请求对象封装 传给引擎,转发给调度器
调度器有 队列,过滤器2个部分,将过滤请求加入到队列中
调度器在把返回的请求通过引擎 给下载器
下载器在上互联网上进行下载,互联网response给下载器
下载器给引擎,引擎给splder,产生item再给引擎 再给管道
引擎作用:解析所有数据,事物触发
=====请求传参抓前5页内容
# -*- coding: utf-8 -*- import scrapy from ..items import MovieproItem class MovieSpider(scrapy.Spider): name = 'movie' # allowed_domains = ['www.xxx.com'] start_urls = ['https://www.4567tv.tv/frim/index1.html'] #通用的url模板只适用于非第一页 url = 'https://www.4567tv.tv/frim/index1-%d.html' page = 2 #电影名称(首页),简介(详情页) def parse(self, response): li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li') for li in li_list: name = li.xpath('./div/a/@title').extract_first()#电影名称 detail_url = 'https://www.4567tv.tv'+li.xpath('./div/a/@href').extract_first()#电影详情url item = MovieproItem() item['name'] = name #对详情页的url发起get请求 #请求传参:meta参数对应的字典就可以传递给请求对象中指定好的回调函数 yield scrapy.Request(url=detail_url,callback=self.detail_parse,meta={'item':item}) if self.page <= 5: new_url = format(self.url%self.page)#第二页 self.page += 1 yield scrapy.Request(url=new_url,callback=self.parse)#再次调page #解析详情页的页面数据 def detail_parse(self,response): #回调函数内部通过response.meta就可以接收到请求传参传递过来的字典 item = response.meta['item'] desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first() item['desc'] = desc yield item
ua伪装 代理ip
下载中间件:批量拦截所有的请求和相应啊
拦截请求: UA伪装 代理ip
from scrapy import signals import random #批量拦截所有的请求和响应 class MiddlewearproDownloaderMiddleware(object): #UA池 user_agent_list = [ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 " "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1", "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 " "(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11", ] #代理池 PROXY_http = [ '153.180.102.104:80', '195.208.131.189:56055', ] PROXY_https = [ '120.83.49.90:9000', '95.189.112.214:35508', ] #拦截正常请求:request就是该方法拦截到的请求,spider就是爬虫类实例化的一个对象 def process_request(self, request, spider): print('this is process_request!!!') #UA伪装 request.headers['User-Agent'] = random.choice(self.user_agent_list) return None #拦截所有的响应 def process_response(self, request, response, spider): return response #拦截发生异常的请求对象 def process_exception(self, request, exception, spider): print('this is process_exception!!!!') #代理ip的设定 if request.url.split(':')[0] == 'http': request.meta['proxy'] = random.choice(self.PROXY_http) else: request.meta['proxy'] = random.choice(self.PROXY_https) #将修正后的请求对象重新进行请求发送 return request
scrapy+selenium 进行相应篡改
(慢死 有时候还不对)个人评价
scrapy中应用selenium的编码流程: - 爬虫类中定义一个属性bro - 爬虫类中重写父类的一个方法closed,在该方法中关闭bro - 在中间件类的process_response中编写selenium自动化的相关操作
wangyi163.py
import scrapy from ..items import WangyiItem from selenium import webdriver class Wangye163Spider(scrapy.Spider): name = 'wangye163' # allowed_domains = ['www.163.com'] start_urls = ['https://news.163.com/'] # 浏览器实例化的操作只会被执行一次 bro = webdriver.Chrome(executable_path='D:老男孩老师笔记第六阶段爬虫pachong练习day06wangyiwangyispiderschromedriver.exe') urls = [] # 5大板块对应的url 发送给中间件 def parse(self, response): li_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li') for index in [3, 4, 6, 7, 8]: li = li_list[index] # 查找第几个位子 new_url = li.xpath('./a/@href').extract_first() print(new_url,1111) self.urls.append(new_url) # 五大板块对应的url进行请求发送 yield scrapy.Request(url=new_url, callback=self.parse_news) # 用来解析每一个板块对应的新闻数据(新闻标题) def parse_news(self, response): div_list = response.xpath('//div[@class="ndi_main"]/div') for div in div_list: try: title = div.xpath('.//div[@class="news_title"]/h3/a/text()').extract_first() except: continue new_detail_url = div.xpath('.//div[@class="news_title"]/h3/a/@href').extract_first() # 实例化item对象将解析内容存储到item对象中 item = WangyiItem() item['title'] = title # 对详情页的url手动请求发送以便回去新闻内容 content在另一个解析对象中,所以要进行传参 yield scrapy.Request(url=new_detail_url, callback=self.parse_detail, meta={'item': item}) def parse_detail(self, response): # 解析方法 item = response.meta['item'] # 这时候就可以拿到item对象了 # 通过response解析出新闻内容 content = response.xpath('//div[@id="endText"]/p//text()').extract() if not content: content = response.xpath('//div[@class="viewport"]/div[@class="overview"]/p/text()').extract_first() # else: # content = response.xpath('//div[@id="endText"]//text()').extract() content = ''.join(content).strip() item['content'] = content yield item def closed(self, spider): # 重写父类的关闭方案法 print('爬虫系统结束') self.bro.quit()
中间件
from scrapy import signals from scrapy.http import HtmlResponse from time import sleep class WangyiDownloaderMiddleware(object): def process_request(self, request, spider): return None #在5五大板块中的url 如果是拦击response def process_response(self, request, response, spider): if request.url in spider.urls: # 爬虫类中spider对赢得urls属性 # 就要对其对应的相应对象进行处理 # 获取了在爬中类中定义好的浏览器对象 bro = spider.bro bro.get(request.url) # 获取携带了新闻数据页面源码数据 ''' bro.execute_script('window.scrollTo(0,document.body.scrollHeight)') sleep(1) bro.execute_script('window.scrollTo(0,document.body.scrollHeight)') sleep(1) bro.execute_script('window.scrollTo(0,document.body.scrollHeight)') sleep(1) bro.execute_script('window.scrollTo(0,document.body.scrollHeight)') sleep(1) ''' page_text = bro.page_source # 实例化一个新的响应对象 age_text是符合要求的里面包含了动态数据 new_response = HtmlResponse(url=request.url, body=page_text, encoding='utf-8', request=request) #满足条件的response return new_response else: return response def process_exception(self, request, exception, spider): pass def spider_opened(self, spider): spider.logger.info('Spider opened: %s' % spider.name)
CrawlSpider
作用:就是用于进行全站数据的爬取
- CrawlSpider就是Spider的一个子类
- 如何新建一个基于CrawlSpider的爬虫文件
- scrapy genspider -t crawl xxx www.xxx.com
- LinkExtractor连接提取器:根据指定规则(正则)进行连接的提取
- Rule规则解析器:将链接提取器提取到的链接进行请求发送,然后对获取的页面数据进行指定规则(callback)的解析
- 一个链接提取器对应唯一一个规则解析器
dianyin
# -*- coding: utf-8 -*- import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from ..items import SunlineproItem,ContentItem class DianyiSpider(CrawlSpider): name = 'dianyi' # allowed_domains = ['www.xxx.com'] start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page='] link= LinkExtractor(allow=r'type=4&page=d+')#提取页码连接 link1 = LinkExtractor(allow=r'question/2019d+/d+.shtml') # 提取详情页的链接 rules = ( Rule(link, callback='parse_item', follow=False),#等于true就会自动爬取所有页 Rule(link1, callback='parse_detail'), ) #解析出标题和网友名称 def parse_item(self, response): tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr') for tr in tr_list: title = tr.xpath('./td[2]/a[2]/text()').extract_first() netFriend = tr.xpath('./td[4]/text()').extract_first() item = SunlineproItem()#没有办法进行传参请求 所以弄了2个item item['title'] = title item['netFriend'] = netFriend yield item #解析出新闻的内容 def parse_detail(self,response): content = response.xpath('/html/body/div[9]/table[2]//tr[1]/td/text()').extract() content = ''.join(content) print(content,3333333333) item = ContentItem() item['content'] = content yield item
items
import scrapy class SunlineproItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() title=scrapy.Field() netFriend = scrapy.Field() class ContentItem(scrapy.Item): # define the fields for your item here like: content = scrapy.Field()
pipelines
class SunlineproPipeline(object): def process_item(self, item, spider): #接收到的item究竟是什么类型的 if item.__class__.__name__ =='SunlineproItem':#返回当前对象的类名 print(item['title'],item['netFriend']) else: print(item['content'])#不是就取出详情 return item
settings中打开相关 设置
分布式爬取
- 概念:可以将一组程序执行在多台机器上(分布式机群),使其进行数据的分布爬取。
- 原生的scrapy框架是否可以实现分布式?
- 不可以?
- 调度器不可以被分布式机群共享
- 管道不可以被分布式机群共享
- 借助scrapy-redis这个模块帮助scrapy实现分布式
- scrapy-redis作用:
- 可以提供可以被共享的管道和调度器
- pip install scrapy-redis
- 分布式的实现流程:
- 导报:from scrapy_redis.spiders import RedisCrawlSpider
- 修改爬虫文件的代码:
- 将当前爬虫类的父类修改成RedisCrawlSpider
- 将start_urls删除
- 添加一个新属性redis_key = 'ts':可以被共享的调度器中的队列名称
- 设置管道:
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 400
# 'scrapyRedisPro.pipelines.ScrapyredisproPipeline': 300,
}
- 设置调度器:
# 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 使用scrapy-redis组件自己的调度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据
SCHEDULER_PERSIST = True
- 指定redis服务器
REDIS_HOST = '192.168.12.154'
REDIS_PORT = 6379
- 配置redis:
修改Redis的配置文件:redis.windows.conf
- #bind 127.0.0.1
- protected-mode no
- 携带配置文件启动redis服务
- redis-server ./redis.windows.conf
- 启动redis客户端
- 执行工程:scrapy runspider xxx.py
- 手动将起始url扔入调度器的队列中(redis-cli):lpush ts www.xxx.com
- redis-cli: items:xxx
###############
scrapy startproject scrapyRedisPro
scrapy genspider -t crawl test www.x.com
========test.py======
# -*- coding: utf-8 -*- import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from scrapyRedisPro.items import ScrapyredisproItem from scrapy_redis.spiders import RedisCrawlSpider from scrapy_redis.spiders import RedisSpider class TestSpider(RedisCrawlSpider): name = 'test' # allowed_domains = ['www.xxx.com'] # start_urls = ['http://www.xxx.com/'] redis_key = 'ts' #可以被共享的调度器中的队列名称 rules = ( Rule(LinkExtractor(allow=r'type=4&page=d+'), callback='parse_item', follow=True), ) def parse_item(self, response): tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr') for tr in tr_list: title = tr.xpath('./td[2]/a[2]/text()').extract_first() netFriend = tr.xpath('./td[4]/text()').extract_first() item = ScrapyredisproItem() item['title'] = title item['net'] = netFriend yield item #提交的item必须保证提交到可以被共享的管道中
=====items=====
import scrapy class ScrapyredisproItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() title = scrapy.Field() net = scrapy.Field()
====settings====
ITEM_PIPELINES = { # 'scrapyRedisPro.pipelines.ScrapyredisproPipeline': 300, 'scrapy_redis.pipelines.RedisPipeline': 400 } # 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 使用scrapy-redis组件自己的调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据 SCHEDULER_PERSIST = True REDIS_HOST = '127.0.0.1' REDIS_PORT = 6379 CONCURRENT_REQUESTS = 2#请求进程
D:老男孩老师笔记第六阶段爬虫pachong练习day07scrapyRedisProscrapyRedisProspiders>scrapy runspider test.py #开启
C:UsersAdministrator>redis-cli
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> ^C
C:UsersAdministrator>redis-cli -h 127.0.0.1
127.0.0.1:6379> lpush ts http://wz.sun0769.com/index.php/question/questionType?type=4&page=
127.0.0.1:6379> lrange test:items 0 -1
url增量爬虫
- 概念:监测网上数据更新的情况。
- 数据指纹
判断网站详情页的url是否进行请求发送
########dianying.py#############
# -*- coding: utf-8 -*- import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from ..items import ScrapydianyingItem from redis import Redis class DianyingSpider(CrawlSpider): name = 'dianying' # allowed_domains = ['www.xxx.com'] start_urls = ['https://www.4567tv.tv/frim/index1.html'] link = LinkExtractor(allow=r'/frim/index1-d+.html') rules = ( Rule(link, callback='parse_item', follow=False),#只爬取前几页 ) conn = Redis(host='127.0.0.1', port=6379) # 解析电影的名称和详情页的url def parse_item(self, response): li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li') for li in li_list: title = li.xpath('./div/a/@title').extract_first() detail_url = 'https://www.4567tv.tv' + li.xpath('./div/a/@href').extract_first() item = ScrapydianyingItem() item['title'] = title # 判断该详情页的url是否进行请求发送 ex = self.conn.sadd('movie_detail_urls', detail_url) if ex == 1: # 说明detail_url不存在于redis的set中 print('已有最新数据更新,请爬......') yield scrapy.Request(url=detail_url, callback=self.parse_detail, meta={'item': item}) else: print('暂无新数据的更新!!!') def parse_detail(self, response): item = response.meta['item'] desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first() item['desc'] = desc yield item
########items############
class ScrapydianyingItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() title=scrapy.Field() desc=scrapy.Field()
###########pipelines##########
class ScrapydianyingPipeline(object): def process_item(self, item, spider): dic = { 'title':item['title'], 'desc':item['desc'] } conn = spider.conn conn.lpush('movie_data',dic) return item
##客户端连接redis进行验证
127.0.0.1:6379> keys *
1) "movie_detail_urls"
2) "movie_data"
127.0.0.1:6379> llen movie_data
(integer) 144
127.0.0.1:6379> smembers movie_detail_urls
数据增量爬虫
- 需求:爬取糗事百科中的段子和作者数据。
#############qiubai.py#############
# -*- coding: utf-8 -*- import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from redis import Redis import hashlib from ..items import BaikeItem class QiubaiSpider(CrawlSpider): name = 'qiubai' # allowed_domains = ['www.baid.com'] start_urls = ['https://www.qiushibaike.com/text/'] rules = ( Rule(LinkExtractor(allow=r'/text/page/d+/'), callback='parse_item', follow=False), ) # 创建redis链接对象 conn = Redis(host='127.0.0.1', port=6379) def parse_item(self, response): div_list = response.xpath('//div[@id="content-left"]/div') for div in div_list: item = BaikeItem() item['author'] = div.xpath('./div[1]/a[2]/h2/text() | ./div[1]/span[2]/h2/text()').extract_first() item['content'] = div.xpath('.//div[@class="content"]/span/text()').extract_first() # 将解析到的数据值生成一个唯一的标识进行redis存储 source = item['author'] + item['content'] source_id = hashlib.sha256(source.encode()).hexdigest() # 将解析内容的唯一表示存储到redis的data_id中 ex = self.conn.sadd('data_id', source_id) if ex == 1: print('该条数据没有爬取过,可以爬取......') yield item else: print('该条数据已经爬取过了,不需要再次爬取了!!!')
###########items.py###########
import scrapy class BaikeItem(scrapy.Item): # define the fields for your item here like: author=scrapy.Field() content=scrapy.Field()
#########pipelines.py#############
class BaikePipeline(object): def process_item(self, item, spider): dic = { 'title':item['author'], 'desc':item['content'] } conn = spider.conn conn.lpush('qiubaiData',dic) return item
D:老男孩老师笔记第六阶段爬虫pachong练习day07aike>scrapy crawl qiubai 执行