內容回顧
- 模擬登錄:
- sometimes咱們須要爬取基於當前用戶的用戶信息(須要登陸後纔可查看)
- 實現流程:
- 藉助於抓包工具,抓取點擊登陸按鈕發起的post請求(url,參數(動態參數))
- 攜帶cookie對其餘子頁面進行請求發送
- 反爬機制:
- robots
- UA檢測
- 驗證碼
- cookie
- 禁ip
- 動態請求參數
在程序中 是否能夠一味的使用多線程,多進程?php
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秒html
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)
概念:是一個基於瀏覽器自動話的模塊.python
和爬蟲之間的關聯?mysql
幫咱們便捷的爬取到頁面中動態加載出來的數據jquery
實現模擬登錄linux
使用流程:web
pip install selenium
下載對應的驅動程序:http://chromedriver.storage.googleapis.com/index.htmlredis
查看對應的版本:https://blog.csdn.net/huilan_same/article/details/51896672sql
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()
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()
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()
pip install Pillowchrome
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]] '''
框架:集成了不少功能且具備很強通用性的一個項目模版
如何學習框架:
學習框架的功能模塊的具體使用
環境的安裝:
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 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+'\n') 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 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請求的發送:(麻煩少用,登錄用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
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的編碼流程: - 爬蟲類中定義一個屬性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練習\day06\wangyi\wangyi\spiders\chromedriver.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就是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/2019\d+/\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練習\day07\scrapyRedisPro\scrapyRedisPro\spiders>scrapy runspider test.py #開啓
C:\Users\Administrator>redis-cli
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> ^C
C:\Users\Administrator>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是否進行請求發送
########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練習\day07\baike>scrapy crawl qiubai 執行