Python爬蟲開發手冊
阿新 • • 發佈:2021-01-10
python爬蟲開發手冊
第一章 requests模組
1.1 指定URL
-
UA偽裝
headers = { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36' }
1.2 請求
-
請求引數
# get請求 params = { '引數': '值', } # post請求 data = { '引數': '值', }
-
get請求
response = requests.get(url=url, params=params, headers=headers)
-
post請求
response = requests.post(url=url, data=data, headers=headers)
-
關於字元編碼
# 方法一:手動設定響應資料的編碼格式 response.encoding = 'gbk' # 方法二:字串統一編碼(通用) str_name = str_name.encode('iso-8859-1').decode('gbk')
-
關於請求失敗
def get_response(url): try: response = requests.get(url=url, headers=headers, timeout=30) if response.status_code == 200: return response except Exception: for i in range(1, 10): print(f'請求{url}超時,第{i}次重複請求') response = requests.get(url, headers=headers, timeout=30) if response.status_code == 200: return response
1.3 響應
-
響應型別
# 字串 response.text # 二進位制 response.content # json物件 json_obj = response.json() # json序列化時對中文預設使用的ascii編碼.想輸出真正的中文需要指定ensure_ascii=False json.dump(json_obj, fp=fp, ensure_ascii=False)
1.4 解析:正則、bs4、XPath
-
正則
page_text = requests.get(url=url, headers=headers).text ''' <div class="thumb"> <a href="/article/121721100" target="_blank"> <img src="//pic.qiushibaike.com/system/pictures/12172/121721100/medium/DNXDX9TZ8SDU6OK2.jpg" alt="指引我有前進的方向"> </a> </div> ''' ex = '<div class="thumb">.*?<img src="(.*?)" alt.*?</div>' # 如果不使用re.S引數,則只在每一行內進行匹配,如果一行沒有,就換下一行重新開始。 # re.S:單行匹配,將整個字串作為一個整體,在整體中進行匹配;讓.匹配包括換行在內的所有字元 # re.M:多行匹配,將字串按\n分開;讓^匹配每行的開頭,$匹配每行的結尾 re.findall(ex, page_text, re.S)
-
bs4
from bs4 import BeautifulSoup # 物件例項化:1.本地html文件 with open('./test.html','r',encoding='utf-8') as fp: soup = BeautifulSoup(fp,'lxml') # 物件例項化:2.網際網路上獲取的頁面原始碼 page_text = response.text soup = BeatifulSoup(page_text,'lxml') # 方法和屬性: soup.div # 首個div標籤 soup.find('div') # 首個div標籤 soup.find('div', class_='song') # 首個class='song'的div標籤 soup.find_all('div') # 所有div標籤(列表) # select選擇器 soup.select('div') # 所有div標籤(列表) soup.select('.tang>ul>li>a') # >表示一個層級 soup.select('.tang>ul a') # 空格表示多個層級 # 獲取標籤內文字資料 soup.a.text # 標籤內所有文字 soup.a.get_text() # 標籤內所有文字 soup.a.string # 標籤下直系文字 # 獲取標籤中屬性值 soup.a['href'] # a標籤的href屬性值
-
XPath
from lxml import etree # 物件例項化:1.本地html文件 etree.parse('./test.html') # 物件例項化:2.網際網路上獲取的頁面原始碼 page_text = response.text tree = etree.HTML(page_text) # /是一個層級,從根節點開始定位。 tree.xpath('/html/body/div') # //是多個層級,從任意位置開始定位。 tree.xpath('/html//div') tree.xpath('//div') # ./,從當前層級開始定位。 tree.xpath('./div') # 屬性定位 tree.xpath('//div[@class="song"]') # 索引定位(索引從1開始) tree.xpath('//div[@class="song"]/p[3]') # 取文字:/text() 標籤中直系文字 tree.xpath('//div[@class="tang"]//li[5]/a/text()') # 取文字://text() 標籤中所有文字 tree.xpath('//li[7]//text()') tree.xpath('//div[@class="tang"]//text()') # 取屬性:@attrName r = tree.xpath('//div[@class="song"]/img/@src') # xpath表示式 tree.xpath('//div[@class="bottom"]/ul/li/') # 熱門城市a標籤的層級關係 tree.xpath('//div[@class="bottom"]/ul/div[2]/li/a') # 全部城市a標籤的層級關係 # 全部城市a標籤的層級關係 tree.xpath('//div[@class="bottom"]/ul/li/a | //div[@class="bottom"]/ul/div[2]/li/a')
1.5 儲存
-
文字
page_text = response.text # 文字 with open('./a.txt', 'w', encoding='utf-8') as fp: fp.write(page_text)
-
圖片
img_data = response.content # 二進位制格式 with open('./qiutu.jpg','wb') as fp: fp.write(img_data)
-
json物件
list_data = response.json() # json物件 with open('./douban.json','w',encoding='utf-8') as fp: json.dump(list_data, fp=fp, ensure_ascii=False)
第二章 驗證碼
2.1 滑動驗證碼
- https://www.cnblogs.com/ohahastudy/p/11493971.html
- https://www.cnblogs.com/ohahastudy/p/11518256.html
第三章 代理
3.1 代理相關的網站
-
快代理
-
西祠代理
-
全網代理IP
# 網址 www.goubanjia.com # 代理ip的匿名度 透明:伺服器知道該次請求使用了代理,也知道請求對應的真實ip 匿名:知道使用了代理,不知道真實ip 高匿:不知道使用了代理,更不知道真實的ip
3.2 設定代理
-
設定代理
import requests # 先獲取代理IP response = requests.get(url=url, headers=headers, proxies={"https": '36.25.40.214:8888'})
第四章 session
4.1 模擬登入
-
模擬登入
import requests # 建立一個session物件 session = requests.Session() # session物件對url傳送get請求,攜帶了cookie page_text = session.get(url=url, headers=headers).text # 使用session物件進行模擬登入post請求的傳送,cookie就會被儲存在session中 response = session.post(url=post_url, data=data, headers=headers) print(response.status_code)
第五章 高效能非同步
5.1 多程序、多執行緒
- 優點:可以為相關阻塞的操作單獨開啟執行緒或者程序,阻塞操作可以非同步執行。
- 缺點:無法無限制的開啟多執行緒或者多程序。
5.2 程序池、執行緒池
-
優點:可以降低系統對程序或者執行緒建立和銷燬的頻率,從而很好的降低系統的開銷。
-
缺點:池中執行緒或程序的數量是有上限。
from multiprocessing.dummy import Pool def get_video_data(dic): url = dic['url'] name = dic['name'] data = requests.get(url=url, headers=headers).content with open(name, 'wb') as fp: fp.write(data) dic = { 'name': name, 'url': video_url } urls.append(dic) # 使用執行緒池對視訊資料進行請求(較為耗時的阻塞操作) pool = Pool(4) pool.map(get_video_data, urls) pool.close() pool.join()
from concurrent.futures import ThreadPoolExecutor def get_video_data(dic): url = dic['url'] name = dic['name'] data = requests.get(url=url, headers=headers).content with open(name, 'wb') as fp: fp.write(data) dic = { 'name': name, 'url': video_url } urls.append(dic) # 例項化一個執行緒池物件 pool = ThreadPoolExecutor(4) for dic in urls: pool.submit(get_video_data, dic) # 等所有任務完成,再關閉執行緒池 pool.shutdown(wait=True)
5.3 單執行緒+非同步協程
- 詳情見第六章
第六章 協程爬蟲
6.1 gevent模組
-
gevent模組屬於第三方模組
from gevent import monkey;monkey.patch_all() import gevent import time def eat(name): print('%s eat 1' %name) gevent.sleep(2) # gevent可以識別的io阻塞 print('%s eat 2' %name) def play(name): print('%s play 1' %name) time.sleep(1) # gevent不能直接識別,需要首行打補丁 print('%s play 2' %name) g1=gevent.spawn(eat,'egon') g2=gevent.spawn(play,name='egon') # 或者gevent.joinall([g1,g2]) g1.join() g2.join() print('主')
6.2 asyncio模組
-
asyncio模組是python內建的模組
import asyncio """ 直接呼叫 """ # 用async定義一個協程 async def request(url): print('正在請求的url是', url) print('請求成功,', url) return url # 被async修飾的函式,呼叫之後返回一個 協程物件c c = request('www.baidu.com') # 1. 基於asyncio 建立一個 事件迴圈物件loop loop = asyncio.get_event_loop() # 2. 將 協程物件c 註冊到 事件迴圈物件loop 中,然後啟動loop loop.run_until_complete(c)
import asyncio """ 通過task物件呼叫 """ # 用async定義一個協程 async def request(url): print('正在請求的url是', url) print('請求成功,', url) return url # 被async修飾的函式,呼叫之後返回一個協程物件c c = request('www.baidu.com') # 1. 基於asyncio 建立一個 事件迴圈物件loop loop = asyncio.get_event_loop() # 2. 基於loop 建立一個 task物件 task = loop.create_task(c) # 3. 將 task物件 註冊到 事件迴圈物件loop 中,然後啟動loop print(task) loop.run_until_complete(task) print(task)
import asyncio """ 通過future物件呼叫 """ # 用async定義一個協程 async def request(url): print('正在請求的url是', url) print('請求成功,', url) return url # 被async修飾的函式,呼叫之後返回一個協程物件c c = request('www.baidu.com') # 1. 基於asyncio 建立一個 事件迴圈物件loop loop = asyncio.get_event_loop() # 2. 基於asyncio 建立一個 future物件 future = asyncio.ensure_future(c) # 3. 將 future物件 註冊到 事件迴圈物件loop 中,然後啟動loop print(future) loop.run_until_complete(future) print(future)
import asyncio """ 呼叫回撥函式 """ # 用async定義一個協程 async def request(url): print('正在請求的url是', url) print('請求成功,', url) return url def callback_func(future): # result()返回的是任務物件中封裝的協程物件對應函式的返回值 print(future.result()) # 被async修飾的函式,呼叫之後返回一個 協程物件c c = request('www.baidu.com') # 1. 基於asyncio 建立一個 事件迴圈物件loop loop = asyncio.get_event_loop() # 2. 基於asyncio 建立一個 future物件 future = asyncio.ensure_future(c) # 3. 將 回撥函式 繫結到 future物件 中 future.add_done_callback(callback_func) # 4. 將 future物件 註冊到 事件迴圈物件loop 中,然後啟動loop loop.run_until_complete(future)
-
示例一:模擬
import asyncio # 用async定義一個協程 async def request(url): print('正在下載', url) # 非同步協程中出現同步模組相關的程式碼,無法實現非同步。 # time.sleep(2) # 在asyncio中遇到阻塞操作,用await進行手動掛起 await asyncio.sleep(2) print('下載完畢', url) urls = [ 'www.baidu.com', 'www.sogou.com', 'www.goubanjia.com' ] # 任務列表:存放多個任務物件 tasks = [] for url in urls: c = request(url) task = asyncio.ensure_future(c) tasks.append(task) loop = asyncio.get_event_loop() # 將任務列表封裝到wait中 loop.run_until_complete(asyncio.wait(tasks))
-
示例二:訪問flask伺服器
import requests import asyncio import aiohttp async def get_page(url): async with aiohttp.ClientSession() as session: # get()、post(): # headers,params/data,proxy='http://ip:port' async with await session.get(url=url) as response: # text()返回字串形式的響應資料 # read()返回的二進位制形式的響應資料 # json()返回的就是json物件 # 注意:獲取響應資料前,要用await進行手動掛起 page_text = await response.text() print(page_text) urls = [ 'www.baidu.com', 'www.sogou.com', 'www.goubanjia.com' ] # 任務列表:存放多個任務物件 tasks = [] for url in urls: c = get_page(url) task = asyncio.ensure_future(c) tasks.append(task) loop = asyncio.get_event_loop() # 將任務列表封裝到wait中 loop.run_until_complete(asyncio.wait(tasks))
-
示例三:訪問網頁
import asyncio import aiohttp class GetArticle(object): def __init__(self, url, file): pass async def article2dict(self, menu_dict): pass # 非同步協程:請求文章URL的text內容 async with aiohttp.ClientSession() as session: try: async with await session.get(url=article_url, headers=self.headers, timeout=5) as response: # 使用gbk進行解碼 article_text = await response.text(encoding='gbk') except Exception: for i in range(1, 10): print(f'請求 {article_url} 超時,第{i}次重複請求') async with await session.get(url=article_url, headers=self.headers, timeout=5) as response: article_text = await response.text(encoding='gbk') print(f'第{i}次重複請求 {article_url} 成功') # 訪問成功,跳出迴圈 break pass def run(self): """ 主函式 :return: """ pass # 任務列表:存放多個任務物件 tasks = [] for menu_dict in menu_list: c = self.article2dict(menu_dict) task = asyncio.ensure_future(c) tasks.append(task) loop = asyncio.get_event_loop() # 將任務列表封裝到wait中 loop.run_until_complete(asyncio.wait(tasks)) pass
第七章 selenium模組
7.1 瀏覽器驅動
7.2 基本使用方法
-
示例一:
from selenium import webdriver # 例項化一個瀏覽器物件(傳入瀏覽器的驅動程式) bro = webdriver.Chrome(executable_path='./chromedriver') """ browser = webdriver.Firefox() browser = webdriver.Edge() browser = webdriver.PhantomJS() browser = webdriver.Safari() """ # 瀏覽器發起請求:get bro.get('https://www.taobao.com/') # 獲取當前頁面的原始碼資料:page_source page_text = bro.page_source # 標籤定位:find_element...,返回標籤 search_input = bro.find_element_by_id('q') # 標籤定位:find_elements...,返回標籤列表 search_input = bro.find_elements_by_id('q') # 標籤互動:send_keys、clear search_input.send_keys('iPhone') search_input.clear() # 執行一組js程式:execute_script bro.execute_script('window.scrollTo(0, document.body.scrollHeight)') # 點選搜尋按鈕 btn = bro.find_element_by_css_selector('.btn-search') btn.click() # 瀏覽器發起新的請求:get bro.get('https://www.baidu.com') # 回退 bro.back() # 前進 bro.forward() # 關閉瀏覽器 bro.quit()
7.3 動作鏈 + iframe處理
-
示例一:
from selenium import webdriver # 匯入動作鏈對應的類 from selenium.webdriver import ActionChains bro = webdriver.Chrome(executable_path='./chromedriver') bro.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable') # 定位的標籤存在於iframe標籤之中,必須切換瀏覽器標籤定位的作用域(通過id查詢iframe) bro.switch_to.frame('iframeResult') div = bro.find_element_by_id('draggable') # 動作鏈 action = ActionChains(bro) # 點選並長按指定的標籤 action.click_and_hold(div) for i in range(5): # move_by_offset(x,y):x水平方向 y豎直方向;perform()立即執行動作鏈操作 action.move_by_offset(17, 0).perform() # 釋放動作鏈 action.release() # 關閉瀏覽器 bro.quit()
-
示例二:模擬登入QQ空間
from selenium import webdriver from time import sleep bro = webdriver.Chrome(executable_path='./chromedriver') bro.get('https://qzone.qq.com/') # 切換到iframe作用域 bro.switch_to.frame('login_frame') # 切換到賬號密碼登入 a_tag = bro.find_element_by_id("switcher_plogin") a_tag.click() # 賬號密碼 userName_tag = bro.find_element_by_id('u') password_tag = bro.find_element_by_id('p') sleep(1) userName_tag.send_keys('*******') sleep(1) password_tag.send_keys('*******') sleep(1) btn = bro.find_element_by_id('login_button') btn.click() sleep(5) # 關閉瀏覽器 bro.quit()
7.4 無視覺化頁面 + 規避檢測
-
示例一:
from selenium import webdriver from time import sleep # 無視覺化介面 from selenium.webdriver.chrome.options import Options # 規避檢測 from selenium.webdriver import ChromeOptions # 無視覺化介面 chrome_options = Options() chrome_options.add_argument('--headless') chrome_options.add_argument('--disable-gpu') # 規避檢測 option = ChromeOptions() option.add_experimental_option('excludeSwitches', ['enable-automation']) # 實現selenium:無視覺化介面 + 規避檢測 bro = webdriver.Chrome(executable_path='./chromedriver', chrome_options=chrome_options, options=option) bro.get('https://www.baidu.com') print(bro.page_source) bro.quit()
7.5 12306登入示例
-
示例一:
from selenium import webdriver import time from PIL import Image from selenium.webdriver import ActionChains bro = webdriver.Chrome(executable_path='./chromedriver') bro.get('https://kyfw.12306.cn/otn/login/init') time.sleep(1) # 找到驗證碼圖片標籤 code_img_ele = bro.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img') # 圖片標籤左上角座標:{'x': 366, 'y': 274} location = code_img_ele.location # 圖片標籤尺寸:{'height': 190, 'width': 293} size = code_img_ele.size # 圖片標籤左上角和右下角座標 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') # 根據指定區域對圖片進行裁剪 frame = i.crop(rangle) frame.save('./code.png') # 將驗證碼圖片提交給超級鷹進行識別 chaojiying = Chaojiying_Client('賬號', '密碼', '軟體ID') im = open('code.png', 'rb').read() result = chaojiying.PostPic(im, 9004)['pic_str'] # 要儲存即將被點選的點的座標:[[x1, y1], [x2, y2]] all_list = [] if '|' in result: list_1 = result.split('|') count_1 = len(list_1) for i in range(count_1): xy_list = [] x = int(list_1[i].split(',')[0]) y = int(list_1[i].split(',')[1]) xy_list.append(x) xy_list.append(y) all_list.append(xy_list) else: x = int(result.split(',')[0]) y = int(result.split(',')[1]) xy_list = [] xy_list.append(x) xy_list.append(y) all_list.append(xy_list) # 遍歷列表,使用動作鏈對每一個列表元素對應的x,y指定的位置進行點選操作 for l in all_list: x = l[0] y = l[1] # 找到對應的標籤,並移動到選定座標,再執行點選動作 ActionChains(bro).move_to_element_with_offset(code_img_ele, x, y).click().perform() time.sleep(1) # 賬號密碼 bro.find_element_by_id('username').send_keys('[email protected]') time.sleep(2) bro.find_element_by_id('password').send_keys('bobo_15027900535') time.sleep(2) bro.find_element_by_id('loginSub').click() time.sleep(30) # 關閉瀏覽器 bro.quit()
7.6 phantomJS模組
-
PhantomJS是一款無介面的瀏覽器,其自動化操作流程和上述操作谷歌瀏覽器是一致的。由於是無介面的,為了能夠展示自動化操作流程,PhantomJS為使用者提供了一個截圖的功能,使用save_screenshot函式實現。
from selenium import webdriver # phantomjs路徑 bro = webdriver.PhantomJS('PhantomJS驅動路徑') # 開啟百度 bro.get('http://www.baidu.com/') # 截圖 bro.save_screenshot(r'phantomjs\baidu.png')
7.7 Pyppeteer模組
-
示例一:
import asyncio from pyppeteer import launch from lxml import etree async def main(): # 新建一個 bro 瀏覽器物件:headless無頭模式、args關閉提示條:”Chrome 正受到自動測試軟體的控制” bro = await launch(headless=False, args=['--disable-infobars']) # 在瀏覽器中新建了一個選項卡 page = await bro.newPage() # page 物件呼叫了 goto 方法就相當於在瀏覽器中輸入了這個 URL await page.goto('http://quotes.toscrape.com/js/') # 當前瀏覽器頁面的原始碼 page_text = await page.content() tree = etree.HTML(page_text) div_list = tree.xpath('//div[@class="quote"]') print(len(div_list)) await bro.close() asyncio.get_event_loop().run_until_complete(main())
第八章 scrapy框架
8.1 基本使用方法
-
建立一個專案
scrapy startproject '專案名'
-
建立一個爬蟲檔案
cd '專案名' scrapy genspider '爬蟲檔名' www.xxx.com
-
啟動專案
scrapy crawl '爬蟲檔名'
8.2 基本配置
-
settings.py
# UA偽裝 USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36' # robots協議 ROBOTSTXT_OBEY = False # 日誌級別 LOG_LEVEL = 'ERROR'
-
爬蟲檔案.py
class TxtSpider(scrapy.Spider): name = 'txt' # allowed_domains = ['www.xxx.com'] start_urls = ['http://xxx.com'] # 起始爬取的url
8.3 儲存
-
指令
# 執行輸出指定格式進行儲存:將爬取到的資料寫入不同格式的檔案中進行儲存 scrapy crawl '爬蟲名稱' -o 'xxx.json' scrapy crawl '爬蟲名稱' -o 'xxx.xml' scrapy crawl '爬蟲名稱' -o 'xxx.csv'
-
管道
# items.py: # 1、定義資料屬性。 import scrapy class XxxItem(scrapy.Item): name = scrapy.Field() # 爬蟲檔案.py: # 1、將爬取到的資料封裝到items物件中。 # 2、使用yield將items物件提交給pipelines管道。 def parse(self, response): item = XxxItem() item['name'] = name yield item # pipelines.py: # 1、process_item方法中接收item物件。 # 2、將item物件中儲存的資料進行持久化儲存。 class XxxPipeline(object): def __init__(self): self.fp = None # 重寫父類方法:開始爬蟲時,執行一次 def open_spider(self,spider): print('爬蟲開始') self.fp = open('./data.txt', 'w') # 重寫父類方法:該方法會被執行呼叫多次 def process_item(self, item, spider): #將爬蟲程式提交的item進行持久化儲存 self.fp.write(item['author'] + ':' + item['content'] + '\n') return item # 重寫父類方法:結束爬蟲時,執行一次 def close_spider(self,spider): self.fp.close() print('爬蟲結束') class XxxPipeline_2(object): pass # settings.py: # 1、開啟管道 ITEM_PIPELINES = { 'xxx.pipelines.XxxPipeline': 300, # 300表示為優先順序,值越小優先順序越高 'xxx.pipelines.XxxPipeline_2': 200, }
8.4 分頁
-
爬蟲檔案.py
class TxtSpider(scrapy.Spider): name = 'txt' # allowed_domains = ['www.xxx.com'] start_urls = ['http://xxx.com'] # 起始爬取的url # 分頁URL模版 url = 'https://www.qiushibaike.com/text/page/%s/' pageNumMax = 100 # 最大頁碼 pageNum = 1 # 起始頁碼 def parse(self, response): pass # 爬取所有分頁資料 if self.pageNum <= self.pageNumMax: self.pageNum += 1 url = format(self.url % self.pageNum) # 遞迴爬取資料:callback引數的值為回撥函式 yield scrapy.Request(url=url, callback=self.parse)
8.5 詳情頁
-
爬蟲檔案.py
class TxtSpider(scrapy.Spider): name = 'txt' # allowed_domains = ['www.xxx.com'] start_urls = ['http://xxx.com'] # 起始爬取的url # 詳情頁 def parse_detail(self, response): # 呼叫引數 item = response.meta['item'] pass yield item # 當前頁面 def parse(self, response, **kwargs): article_url_list = response.xpath('//div[@class="Volume"]//dd/a/@href').extract() for article_url in article_url_list: pass # 請求傳參:meta={},可以將meta字典傳遞給請求對應的回撥函式 yield scrapy.Request(article_url, callback=self.parse_detail, meta={'item': item})
8.6 提高效率
-
增加併發
# 預設scrapy開啟的併發執行緒為32個 CONCURRENT_REQUESTS = 100
-
降低日誌級別
# 在執行scrapy時,會有大量日誌資訊的輸出,為了減少CPU的使用率。可以設定log輸出資訊為INFO或者ERROR即可 LOG_LEVEL = 'INFO' LOG_LEVEL = 'ERROR'
-
禁止cookie
# 如果不是真的需要cookie,則在scrapy爬取資料時可以禁止cookie從而減少CPU的使用率,提升爬取效率 COOKIES_ENABLED = False
-
禁止重試
# 對失敗的HTTP進行重新請求(重試)會減慢爬取速度,因此可以禁止重試 RETRY_ENABLED = False
-
減少下載超時
# 如果對一個非常慢的連結進行爬取,減少下載超時可以讓卡住的連結快速被放棄,從而提升效率 DOWNLOAD_TIMEOUT = 10 # 超時時間為10s
8.7 圖片
-
settinss.py
# 指定圖片儲存的目錄 IMAGES_STORE = './img'
-
pipelines.py
from scrapy.pipelines.images import ImagesPipeline import scrapy class imgsPileLine(ImagesPipeline): # 根據圖片地址進行圖片資料的請求 def get_media_requests(self, item, info): yield scrapy.Request(item['img_url']) # 指定圖片完整路徑 + 圖片名 def file_path(self, request, response=None, info=None, *, item=None): imgName = request.url.split('/')[-1].split('?')[0] print('下載完成!') return imgName def item_completed(self, results, item, info): # 返回給下一個即將被執行的管道類 return item
8.8 中介軟體(代理IP)
-
settings.py
DOWNLOADER_MIDDLEWARES = { 'xxx.middlewares.XxxDownloaderMiddleware': 543, }
-
middlewares.py
import random class XxxDownloaderMiddleware(object): # 對請求設定隨機的User-Agent 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", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 " "(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 " "(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6", "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 " "(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 " "(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5", "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 " "(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 " "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24", "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 " "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24" ] # 對請求設定隨機的代理 PROXY_http = [ '153.180.102.104:80', '195.208.131.189:56055', ] PROXY_https = [ '120.83.49.90:9000', '95.189.112.214:35508', ] # 攔截請求 def process_request(self, request, spider): # UA偽裝 request.headers['User-Agent'] = random.choice(self.user_agent_list) # 為了驗證代理的操作是否生效 request.meta['proxy'] = 'http://183.146.213.198:80' return None # 攔截所有的響應 def process_response(self, request, response, spider): pass return response # 攔截髮生異常的請求 def process_exception(self, request, exception, spider): if request.url.split(':')[0] == 'http': # 代理 request.meta['proxy'] = 'http://' + random.choice(self.PROXY_http) else: request.meta['proxy'] = 'https://' + random.choice(self.PROXY_https) return request # 將修正之後的請求物件進行重新的請求傳送
8.9 瀏覽器物件selenium
-
爬蟲檔案.py
from selenium import webdriver class TxtSpider(scrapy.Spider): name = 'txt' # allowed_domains = ['www.xxx.com'] start_urls = ['http://xxx.com'] # 起始爬取的url models_urls = [] # 儲存需要動態載入的URL # 例項化一個瀏覽器物件 def __init__(self): self.bro = webdriver.Chrome(executable_path='./瀏覽器驅動路徑') pass # 重寫父類方法:爬蟲結束時被呼叫,關閉瀏覽器物件 def closed(self, spider): self.bro.quit()
-
middlewares.py
from scrapy.http import HtmlResponse from time import sleep class XxxDownloaderMiddleware(object): # 攔截請求 def process_request(self, request, spider): pass return None # 攔截所有的響應 def process_response(self, request, response, spider): # 獲取爬蟲類中定義的瀏覽器物件 bro = spider.bro # 挑選出指定的響應物件進行篡改 if request.url in spider.models_urls: bro.get(request.url) sleep(3) # 包含了動態載入的新聞資料 page_text = bro.page_source # 篡改後的相應物件 new_response = HtmlResponse(url=request.url, body=page_text, encoding='utf-8', request=request) return new_response else: # 其他請求對應的響應物件 return response # 攔截髮生異常的請求 def process_exception(self, request, exception, spider): pass return request
8.10 CrawlSpider類
-
建立一個專案
scrapy startproject '專案名'
-
建立一個CrawlSpider爬蟲檔案
cd '專案名' scrapy genspider -t crawl '爬蟲檔名' www.xxx.com
-
啟動專案
scrapy crawl '爬蟲檔名'
-
爬蟲檔案.py
from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from sunPro.items import SunproItem, DetailItem # 需求:爬取sun網站中的編號,新聞標題,新聞內容,標號 class SunSpider(CrawlSpider): name = 'sun' # allowed_domains = ['www.xxx.com'] start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page='] # 連結提取器:根據指定規則(allow="正則")進行指定連結的提取 # 分頁 link = LinkExtractor(allow=r'type=4&page=\d+') # 詳情頁 link_detail = LinkExtractor(allow=r'question/\d+/\d+\.shtml') # 規則解析器:將連結提取器提取到的連結進行指定規則(callback)的解析操作 rules = ( # follow=True:可以將連結提取器 繼續作用到 連線提取器提取到的連結 所對應的頁面中,即找到所有分頁 Rule(link, callback='parse_item', follow=True), Rule(link_detail, callback='parse_detail') ) # 如下兩個解析方法不可以實現請求傳參,可以依次儲存到兩個item # 解析新聞編號和新聞的標題 def parse_item(self, response): # 注意:xpath表示式中不可以出現tbody標籤 tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr') for tr in tr_list: new_num = tr.xpath('./td[1]/text()').extract_first() new_title = tr.xpath('./td[2]/a[2]/@title').extract_first() item = SunproItem() item['title'] = new_title item['new_num'] = new_num yield item # 解析新聞內容和新聞編號 def parse_detail(self, response): new_id = response.xpath('/html/body/div[9]/table[1]//tr/td[2]/span[2]/text()').extract_first() new_content = response.xpath('/html/body/div[9]/table[2]//tr[1]//text()').extract() new_content = ''.join(new_content) item = DetailItem() item['content'] = new_content item['new_id'] = new_id yield item
第九章 分散式
9.1 scrapy
- 排程器不可以被分散式機群共享
- 管道不可以被分散式機群共享
9.1 scrapy-redis
- 可以給原生的scrapy框架提供可以被共享的管道和排程器
9.2 基本使用方法
-
建立一個專案
scrapy startproject '專案名'
-
建立一個CrawlSpider爬蟲檔案
cd '專案名' scrapy genspider -t crawl '爬蟲檔名' www.xxx.com
-
爬蟲檔案.py
from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from fbsPro.items import FbsproItem from scrapy_redis.spiders import RedisCrawlSpider class FbsSpider(RedisCrawlSpider): name = 'fbs' # 將start_urls和allowed_domains進行註釋 # allowed_domains = ['www.xxx.com'] # start_urls = ['http://www.xxx.com/'] # 新增一個新屬性,可以被共享的排程器佇列的名稱 redis_key = 'sun' # 和CrawlSpider類似 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: new_num = tr.xpath('./td[1]/text()').extract_first() new_title = tr.xpath('./td[2]/a[2]/@title').extract_first() item = FbsproItem() item['title'] = new_title item['new_num'] = new_num yield item
-
settings.py
# 指定scrapy-redis的管道 ITEM_PIPELINES = { '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資料庫 REDIS_HOST = '127.0.0.1' # redis遠端伺服器的ip(修改) REDIS_PORT = 6379
-
修改redis.conf
# 註釋掉bind 127.0.0.1 # IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES # JUST COMMENT THE FOLLOWING LINE. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # bind 127.0.0.1 # 關閉保護模式 protected-mode no
-
結合著配置檔案開啟redis服務
./redis-server ../redis.conf
-
啟動客戶端
./redis-cli
-
執行工程
cd 專案名/spiders scrapy runspider '爬蟲檔案.py'
-
在客戶端的終端,向排程器的佇列中放入一個起始的URL
lpush '可以被共享的排程器佇列的名稱' 'www.xxx.com'
-
爬取到的資料儲存在了redis的proName:items這個資料結構中
9.3 redis基本指令
-
客戶端
keys * # 檢視所有表 lrange proName:items 0 -1 # 檢視所有記錄 llen proName:items # 查看錶中的記錄個數 smembers urls # 檢視所有記錄 # 關閉資料庫 redis-cli -p 埠號 shutdown
第十章 增量式
10.1 基於redis的set資料結構
-
爬蟲檔案.py
import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from redis import Redis from moviePro.items import MovieproItem class MovieSpider(CrawlSpider): name = 'movie' # allowed_domains = ['www.ccc.com'] start_urls = ['https://www.4567tv.tv/frim/index1.html'] rules = ( Rule(LinkExtractor(allow=r'/frim/index1-\d+\.html'), callback='parse_item', follow=True), ) # 1、建立redis連結物件 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: # 獲取詳情頁的url detail_url = 'https://www.4567tv.tv' + li.xpath('./div/a/@href').extract_first() # 2、將詳情頁的url存入redis的set中,實現去重 ex = self.conn.sadd('urls', detail_url) if ex == 1: print('該url沒有被爬取過,可以進行資料的爬取') yield scrapy.Request(url=detail_url, callback=self.parst_detail) else: print('資料爬取過!') # 解析詳情頁中的電影名稱和型別,進行持久化儲存 def parst_detail(self, response): item = MovieproItem() pass yield item