爬蟲學習

Jupyter環境安裝

安裝Anaconda(集成環境), 安裝成功後可以提供一種基於瀏覽器的可視化工具 ---Jupyter.html

什麼是jupyter notebook:

Jupyter Notebook是以網頁的形式打開,能夠在網頁頁面中直接編寫代碼和運行代碼,代碼的運行結果也會直接在代碼塊下顯示。如在編程過程當中須要編寫說明文檔,可在同一個頁面中直接編寫,便於做及時的說明和解釋.python

jupyter notebook 的主要特色:

1, 編程時具備語法高亮, 縮進,tab補全的功能;mysql

2, 可直接經過瀏覽器運行代碼, 同時在代碼塊下方展現運行結果;linux

3, 對代碼編寫說明文檔或語句時, 支持Markdown語法.web

安裝 jupyter notebook :

安裝 jupyter notebook 的前提是須要安裝了Python(3.3或2.7以上版本)面試

經過安裝Anaconda來解決Jupyter Notebook的安裝問題,由於Anaconda已經自動爲你安裝了Jupter Notebook及其餘工具,還有python中超過180個科學包及其依賴項.ajax

一般Anaconda 發行版已經自動安裝了jupyter notebook, 若沒有安裝, 能夠在Windows的Anaconda prompt / macOS的終端中輸入安裝命令:redis

conda install jupyter notebook

運行jupyter notebook

默認端口: 8888sql

cmd 命令行中的指令chrome

-- 在本文件路徑下輸入: jupyter notebook---開啓jupyter服務

瀏覽器會自動開啓jupyter, 其中 ' / ' 表示的根目錄是文件夾的目錄

瀏覽器默認顯示: http://localhost:8888 localhost 指的是本機, 8888 則是端口號.

注意: 開啓服務後, 在操做jupyter notebook 時不能關閉終端, 不然就會斷開與本地服務器的連接.

指定端口啓動:

自定義端口啓動jupyter notebook 能夠在終端輸入如下命令:

jupyter notebook -port port_number

其中 port_number 是自定義端口號, 直接以數字的形式寫在命令中.

啓動服務器不打開瀏覽器:

若想要啓動jupyter notebook 可是不打算當即進入到主頁面, 就無需馬上啓動瀏覽器:

jupyter notebook -no-browser

此時,將會在終端顯示啓動的服務器信息,並在服務器啓動以後,顯示出打開瀏覽器頁面的連接。當你須要啓動瀏覽器頁面時,只須要複製連接,並粘貼在瀏覽器的地址欄中,輕按回車變轉到了你的Jupyter Notebook頁面。

快捷鍵:

1, 向上插入一個cell: a

2, 向下插入一個cell: b

3, 刪除cell: x

4, 將code切換成markdown: m

5, 將markdown切換成code: y

6, 運行cell: shift+enter

7, 查看幫助文檔: shift+tab

8, 自動提示: tab

9, 在markdown中 # 能夠控制字體大小, 可使用HTML標籤更改樣式顏色; 在code中, 一個源文件內的代碼沒有上下之分.

 

爬蟲

是經過編寫程序, 模擬瀏覽器上網, 而後讓其去互聯網上爬取數據的過程.

分類:

增量式:

聚焦爬蟲:

增量式爬蟲:

反爬機制與反反爬策略

反爬:

1, robots.txt協議

2, UA

3, 數據加密

4, 懶加載

5, 代理ip

 

http和https協議:

抓包工具:

requests模塊

代碼編寫流程:

1, 指定url

2, 發起請求

3, 獲取響應對象中的數據

4, 持久化存儲

import requests
url = 'https://www.sogou.com/'
response_obj = requests.get(url=url)
page_text = response_obj.text
with open('./sougou.html', 'w', encoding='utf-8')as fp:
  fp.write(page_text)

 

案例1:

爬取搜狗瀏覽器中的詞條搜索信息

# import requests
# url = 'https://www.sogou.com/web'
# # 封裝參數
# can_shu = input('enter a word:')
# param = {
#     'query': can_shu
# }
# response_obj = requests.get(url=url, params=param)
# page_text = response_obj.content
# fileName = can_shu+'.html'
# with open(fileName, 'wb')as fp:
#     fp.write(page_text)
#     print('over')

 

案例2:

爬取百度翻譯結果

# 案例2 爬取百度翻譯結果
# 注意: 翻譯的結果是局部刷新, 採用ajax異步請求
import requests
# post 請求
url = 'https://fanyi.baidu.com/sug'
can_shu = input('worlds:')
data = {
  'kw': can_shu
}
response_obj = requests.post(url=url, data=data)
# 返回的response對象以json數據類型展現.若以text形式爲字符串, 若以content形式爲二進制.
print(response_obj.json())

 

案例3:

爬取豆瓣電影的詳情數據

from requests
# get 請求
url = 'https://movie.douban.com/j/chart/top_list'
# 動態捕獲電影, 設置一個url字典
param = {
  "type": "5",
"interval_id": "100:90",
"action": "",
"start": "0", # 表示從第'0'索引位置開始
"limit": "1" # 表示爬取一個
}
# 返回一個列表
move_data = requests.get(url=url, params=Parma).json()
print(move_data)

 

案例4:

爬取化妝品公司的生產許可證相關信息

# 案例4
# 反扒機制: UA檢測 --> 反反爬策略UA假裝.
# --- 請求載體的身份標識: User-Agent.請求載體不同, 標識就不同.基於瀏覽器和爬蟲
#     的請求在乎不同, 因此須要假裝成某一瀏覽器請求.
import requests
id_list = []
# post 請求
url = 'http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsList'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36'

}
# 爬取多頁
for page in range(1, 3):
data = {
'on': 'true',
'page': str(page),
'pageSize': '15',
'productName': '',
'conditionType': '1',
'applyname': '',
'applysn': ''
}
data_obj = requests.post(url=url, data=data, headers=headers).json()
# 獲取各個ID值
for dic in data_obj['list']:
id = dic['ID']
id_list.append(id)
print(id_list)

# 經過ID 獲取各個公司的生產許可證相關信息
detail_url = 'http://125.35.6.84:81/xk/itownet/portalAction.do?method=getXkzsById'
for id in id_list:
detail_data = {
'id':id
}
detail_json = requests.post(url=detail_url, data=detail_data, headers=headers).json()
print(detail_json)

 

案例5:

爬取圖片

# 爬取圖片
import requests
url = 'http://d.ifengimg.com/w640_q75/p0.ifengimg.com/pmop/2018/0923/D7D76D3B007F024D2D0964DFE3AD909A68232483_size561_w1080_h1920.jpeg'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36'

}
# 圖片爲二進制數據,能夠用content
img_data = requests.get(url=url, headers=headers).content
with open('./gua_jie.jpg', 'wb') as fp:
  fp.write(img_data)

 

案例6:

正則解析爬取動態加載的圖片

re.M 表示將正則依次作用於每行;

re.S 表示將正則做用於原數據(總體)

# 爬取動態加載的圖片
import requests
import re
import urllib
import os
# 拿到指定頁碼的圖片
url = 'https://www.qiushibaike.com/pic/page/%d/?s=5171142'
start_page = int(input('start page'))
end_page = int(input('end page'))
headers = {
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36'
}
if not os.path.exists('./qiutu'):
  os.mkdir('./qiutu')
for page in range(start_page, end_page):
  new_url = format(url%page)
  page_text = requests.get(url=new_url, headers=headers).text
  # 提取圖片的src屬性
  img_url_list = re.findall('<div class="thumb">.*?<img src="(.*?)" alt=.*?</div>', page_text, re.S)
  # 根據src屬性爬取純圖片
  for img_url in img_url_list:
      img_url = 'http:'+img_url
      imgName = img_url.split('/')[-1]
      imgPath ='qiutu/'+imgName
      urllib.request.urlretrieve(url=img_url, filename=imgPath)
      print('well done!')
print('完成!!!')

 

bs4模塊

環境安裝:

pip3 install bs4

pip3 install lxml

解析原理:

1, 將即將要進行解析的源碼加載到bs對象

2, 調用bs對象中相關的方法或屬性進行源碼中的相關標籤的定位(鎖定即將解析的源碼)

3, 將定位到的標籤之間存在的文本或屬性值獲取到(源碼中的有用數據)

 

案例1:

下載各個章節名稱及其下的文本內容

# bs4模塊
from bs4 import BeautifulSoup
import requests
url = 'http://www.shicimingju.com/book/rulinwaishi.html'
headers = {
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36'
}
page_text = requests.get(url=url, headers=headers).text
# 加載要解析的源碼   lxml 爲解析器
soup = BeautifulSoup(page_text, 'lxml')
# 進行標籤訂位
a_list = soup.select('.book-mulu>ul>li>a')
fp = open('儒林外史.txt', 'w', encoding='utf-8')
# 獲取到標籤之間的有用數據, 並經過該數據下載想要所取得的內容
for a in a_list:
  title = a.string
  detail_url ='http://www.shicimingju.com'+a['href']
  detail_page_text = requests.get(url=detail_url, headers=headers).text
   
  soup = BeautifulSoup(detail_page_text, 'lxml')
  content = soup.find('div', class_='chapter_content').text

  fp.write(title+'\n'+content)
  print(title, '下載完畢')
print('well done!!')
fp.close()

 

xpath模塊:

環境安裝: pip install lxml

xpath 經常使用表達式:

/ 表示一種層級關係

@ 表示屬性定位

圖中p[1] 表示 p 標籤中的第一個 p 標籤.

解析原理:

1, 獲取頁面源碼數據

2, 實例化一個etree的對象, 而且將頁面源碼數據加載到該對象中

3, 調用該對象的xpath方法進行指定標籤訂位

注意: xpath函數必須結合xpath表達式進行標籤訂位和內容捕獲

案例1:

獲取房源信息

# xpath模塊爬取二手房信息

import requests
from lxml import etree

url = 'https://xy.58.com/ershoufang/?PGTID=0d000000-0000-00d2-8801-ee7a9683ca0f&ClickID=2/'
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36'
}
# 獲取頁面源碼數據
page_text = requests.get(url=url, headers=headers).text
# 實例化對象
tree = etree.HTML(page_text)
# li_list 的類型爲Element 類型的列表對象
# 標籤訂位及內容捕獲
li_list = tree.xpath('//ul[@class="house-list-wrap"]/li')
fp = open('fang.csv', 'w', encoding='utf-8')
for li in li_list:
  # 進行局部解析要加 '.'
  xin_xi = li.xpath('./div[2]/p[1]//text()')[1]
  price = li.xpath('./div[3]//text()')
  price = ''.join(price)
  fp.write(xin_xi+":"+price+"\n")
fp.close()
print('well done!!')

 

案例2:

獲取圖片有效信息

# 案例2
# 解析圖片
import requests
from lxml import etree
import os
import urllib

url = 'http://pic.netbian.com/4kdongman/index_2.html'
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36'

}
response = requests.get(url=url, headers=headers)
if not os.path.exists('./imgs'):
  os.mkdir('./imgs')
page_text = response.text
tree = etree.HTML(page_text)
print(tree)
li_list = tree.xpath('//div[@class="slist"]/ul/li')
print(li_list)
for li in li_list:
  img_name = li.xpath('./a/b/text()')[0]
  # 處理中文亂碼
  img_name.encode('iso-8859-1').decode('gbk')
  img_url = 'http://pic.netbian.com'+li.xpath('./a/img/@src')[0]
  print(img_name)
  img_path = './img'+img_name+'.jpg'
  urllib.request.urlretrieve(url=img_url, filename=img_path)
  print(img_path, 'nice!!')
print('over!!')

 

案例3:

煎蛋網圖片下載

當數據加密時(防盜圖),須要解密爬取.

案例4:

爬去免費簡歷模板

# 爬取站長素材的簡歷模板
import random
import requests
from lxml import etree
headers ={
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36'

}
url = 'http://sc.chinaz.com/jianli/free_%d.html'
for page in range(1, 4):
  if page == 1:
      new_url = 'http://sc.chinaz.com/jianli/free.html'
  else:
      new_url = format(url%page)
# 獲取源碼數據
response = requests.get(url=new_url, headers=headers)
response.encoding = 'utf-8'
page_text = response.text
# 實例化etree對象
tree = etree.HTML(page_text)
# 定位標籤
div_list = tree.xpath('//div[@id="container"]/div')
for div in div_list:
  detail_url = div.xpath('./a/@href')[0]
  name = div.xpath('./a/img/@alt')[0]
  detail_page = requests.get(url=detail_url, headers=headers).text
  tree = etree.HTML(detail_page)
  # 鎖定下載地址
  down_list = tree.xpath('//div[@class="clearfix mt20 downlist"]/ul/li/a/@href')
  # 隨機選取一個下載地址
  down_url = random.choice(down_list)
  # 獲取到下載內容的壓縮包
  data = requests.get(url= detail_url, headers=headers).content
  fileName = name+'.rar'
  with open(fileName, 'wb') as fp:
      fp.write(data)
      print('下載成功!!')

注意:​

解決方法:
1, 當請求成功後立刻斷開該次請求. 爲了及時釋放請求池資源,
    ---- 'connection': 'close'
  若首次執行任然報錯, 那就再次執行代碼.
2, 使用代理ip
3, 使用sleep
案例5:

爬取城市名稱

# 解析全部的城市名稱
import requests
from lxml import etree
headers = {

}
# 明確要爬取數據的url地址
url = 'https://www.aqistudy.cn/historydata/'
# 肯定要爬取的總體數據信息
page_text = requests.get(url=url, headers=headers).text
# 把要爬取的數據生成在etree對象中
tree = etree.HTML(page_text)
# 鎖定要爬取信息的具體頁面標籤, '|'該管道符表示左邊成立則執行右邊,管道符左右都要空格.
li_list = tree.xpath('//div[@class="bottom"]/ul/li | //div[@class="bottom"]/ul/div[2]/li')
for li in li_list:
  city_name = li.xpath('./a/text()')[0]
  print(city_name)
案例6:

圖片懶加載;:

# 圖片懶加載(一種反爬機制)
# 首先排除動態加載和url加密, 直接在Elements中找.
# 當圖片還未在可視化範圍內時, 圖片標籤是'src2', 加載後爲'src',
# src2 是一個僞屬性, 在爬取圖片時能夠直接用'src2'做爲標籤, 無需考慮是否在可視化範圍內.
案例7:

代理ip:

# 請求過於頻繁, 有被封ip的風險, 可使用代理ip
# 代理ip的類型必需要和請求url的協議頭保持一致
import requests
url = 'https://www.baidu.com/s?cl=3&wd=ip'
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36'
}
# proxies後爲代理ip的鍵值對
page_text = requests.get(url=url, headers=headers, proxies={'https': '103.203.133.250:8080'}).text
with open('./ip.html', 'w', encoding='utf-8')as fp:
  fp.write(page_text)

 

案例8 驗證碼

藉助雲打碼平臺

註冊普通用戶和開發者用戶

登陸:

登陸普通用戶

登陸開發者用戶

建立一個軟件: 個人軟件->建立軟件

下載示例代碼: 在開發者中心下載最新的DLL(PythonHttp示例下載)

在示例代碼中錄入普通用戶名及密碼等相關變量值.

再編寫爬蟲代碼

案例9:模擬登錄

案例10:

動態數據加載(selenium):

環境安裝: pip install selenium

編碼流程:

導包:

from selenium import webdriver
from time import sleep
# 須要藉助瀏覽器終端的驅動程序, 建立瀏覽器對象
bro = webdriver.Chrome(executable_path=r'D:\chromedriver\chromedriver.exe')
# 標明要使用的瀏覽器
bro.get(url='https://www.baidu.com/')
# 明確搜索框位置
text_input = bro.find_element_by_id('kw')
# 肯定搜索框內要搜索的內容
text_input.send_keys('美少女戰士')
# 點擊搜索
bro.find_element_by_id('su').click()
sleep(4)
# 獲取當前頁面的源碼數據(包括動態加載的數據)
print(bro.page_source)
# 退出
bro.quit()
# 獲取更多的詳情數據
from selenium import webdriver
from time import sleep
url = '想要得到數據的頁面詳細地址'
# 調用驅動程序
bro = webdriver.Chrome(executable_path=r'D:\chromedriver\chromedriver.exe')
bro.get(url)
sleep(3)
# 頁面向下滾動的js代碼(有些頁面數據是在頁面向下滾動時才刷新出來的)
bro.execute_script('window.scrollTo(0, document.body.scrollHeight)')
sleep(3)
bro.execute_script('window.scrollTo(0, document.body.scrollHeight)')
sleep(2)
# 拿到全部頁面數據
page_text = bro.page_source
with open('./douban.html', 'w',encoding='utf-8')as fp:
  fp.write(page_text)
sleep(2)
bro.quit()

注意: PhantomJS是一個無界面的瀏覽器 webdriver.PhantomJS​

 

線程池

from multiprocessing .dummy import Pool

能夠設置多個線程池, 同時爬取多個任務

 

移動端數據爬取

 

scrapy框架:

Scrapy框架是一個爲了爬取網頁數據, 提取結構性數據而編寫的應用框架. 所謂框架就是一個已經繼承各類功能(高性能異步下載, 隊列, 分佈式, 解析, 持久化等)的具備很強通用性的項目模板.

安裝

linux: pip3 install scrapy

Windows:

1, pip3 install wheel  (安裝wheel,爲後面安裝twisted作準備)

2, 下載twisted  http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted 

  注意:原生的scrapy 是不能實現併發功能的 而twisted框架可以於處理併發相關的操做,

  因此scrapy框架是借用twisted中的處理併發功能。

3, 進入下載目錄, 執行 pip3 install Twisted- 17.1 - 0 -cp36-cp36m-win_amd64.whl

  是在twisted下載完成後,在下載目錄下,由終端(cmd)安裝所下載的twisted文件

4, pip3 install pywin32

5, pip3 install scrapy

基礎使用

1, 建立項目:

scrapy startproject 項目名

項目目錄:

spiders目錄(存放爬蟲文件) 的做用: url的指定; 請求的發送; 進行數據爬取解析; item管道的提交

items 文件: 只要涉及持久化存儲的相關的操做, 必需要寫在管道文件。與管道文件結合使用。

pipelines文件(管道文件): 須要接受爬蟲文件提交過來的數據, 並對數據進行持久化存儲

middlewares文件:中間件

settings文件:配置文件

2, 建立爬蟲文件:

先從cmd命令框內進入項目內 再輸入 scrapy genspider 爬蟲文件名 爬取的起始url

如:

執行爬蟲文件: 在終端中輸入: scrapy crawl 爬蟲文件名 (在終端執行會顯示日誌信息,若不輸出日誌文件, 在後面追加 '--nolog' )

當ROBOTS反爬協議生效時, 不能輸出response結果, 此時能夠更改設置文件中的配置:

  ROBOTSTXT = Flase

當UA反爬機制生效時也不能輸出結果, 那麼就須要更改設置, 甚至替換請求載體()的身份標識:

  

# -*- coding: utf-8 -*-
import scrapy

# 該文件做用是進行數據的爬取和解析
class FirstSpider(scrapy.Spider):
  # 爬蟲文件的名稱: 根據爬蟲名稱能夠定位到指定的爬蟲文件
  name = 'first'
  # 容許的域名
  allowed_domains = ['www.baidu.com']
  # 起始URL列表(要爬取的url地址必須在容許的域名下,爲了避免衝突也能夠註釋掉域名)
  start_urls = ['https://www.baidu.com/',...]

  # 用於解析: response就是起始URL對應的響應對象,每執行一次起始url列表中的url,就會調用一次該方法.
  def parse(self, response):
      print(response)
3. 基於終端指令的持久化存儲
  • 保證爬蟲文件的parse方法中有可迭代類型對象(一般爲列表or字典)的返回,該返回值能夠經過終端指令的形式寫入指定格式的文件中進行持久化操做。

執行輸出指定格式進行存儲:將爬取到的數據寫入不一樣格式的文件中進行存儲
  scrapy crawl 爬蟲名稱 -o xxx.json
  scrapy crawl 爬蟲名稱 -o xxx.xml
  scrapy crawl 爬蟲名稱 -o xxx.csv

 

# -*- coding: utf-8 -*-
import scrapy

# 該文件做用是進行數據的爬取和解析
class FirstSpider(scrapy.Spider):
  # 爬蟲文件的名稱: 根據爬蟲名稱能夠定位到指定的爬蟲文件
  name = 'first'
  # 容許的域名
  # allowed_domains = ['www.baidu.com']
  # 起始URL列表(要爬取的url地址必須在容許的域名下,爲了避免衝突也能夠註釋掉域名)
  start_urls = ['https://www.qiushibaike.com/text/']

  # 用於解析: response就是起始URL對應的對象,每執行一次起始url列表中的url
  # 就會調用一次該方法.
  def parse(self, response):
      all_data = []
      # xpath 返回的列表元素類型是Select類型 (scrapy框架可以調用xpath進行高性能的解析)
      div_list = response.xpath('//div[@id="content-left"]/div')
      for div in div_list:
        #extract 可以接收select對象中的參數值
          title = div.xpath('./div[1]/a[2]/h2/text() | ./div/span[2]/h2/text()')[0].extract()
          # 若是能保證列表內只有一個元素, 能夠用extract_first
          # title = div.xpath('./div[1]/a[2]/h2/text() | ./div/span[2]/h2/text()').extract_first()
          print(title)
          content = div.xpath('./a/div/span/text()').extract()
          # 該種狀況不能往數據庫中存儲
          dic = {
              'title': title,
              'content': content
          }
       # 把每個字典中的鍵值都存在列表中。
          all_data.append(dic)
  # 基於終端指令的持久化存儲: 能夠經過終端指令的形式將parse方法的返回值中存儲的數據進行本地磁盤的持久化存儲.
      return all_data

 基於終端指令的存儲命令: scrapy crawl 爬蟲文件名 -o 文件名.後綴

4.基於管道的持久化存儲

scrapy框架中已經爲咱們專門集成好了高效、便捷的持久化操做功能,咱們直接使用便可。要想使用scrapy的持久化操做功能,咱們首先來認識以下兩個文件:

    items.py:數據結構模板文件。定義數據屬性。
  pipelines.py:管道文件。接收數據(items),進行持久化操做。

持久化流程:
  1.獲取解析到的數據值
  2.將解析的數據封裝到item對象中(item類中進行相關屬性的聲明)。
  3.使用yield關鍵字將item對象提交到管道進行持久化操做。
  4.在管道文件中的process_item方法中接收爬蟲文件提交過來的item對象,而後編寫持久化存儲的代碼將item對象中存儲的數據進行持久化存儲
  5.settings.py配置文件中開啓管道

將糗事百科首頁中的段子和做者數據爬取下來,而後進行持久化存儲

- 爬蟲文件:qiubaiDemo.py

# -*- coding: utf-8 -*-
import scrapy
from secondblood.items import SecondbloodItem

class QiubaidemoSpider(scrapy.Spider):
   name = 'qiubaiDemo'
   allowed_domains = ['www.qiushibaike.com']
   start_urls = ['http://www.qiushibaike.com/']

   def parse(self, response):
       odiv = response.xpath('//div[@id="content-left"]/div')
       for div in odiv:
           # xpath函數返回的爲列表,列表中存放的數據爲Selector類型的數據。咱們解析到的內容被封裝在了Selector對象中,須要調用extract()函數將解析的內容從Selecor中取出。
           author = div.xpath('.//div[@class="author clearfix"]//h2/text()').extract_first()
           author = author.strip('\n')#過濾空行
           content = div.xpath('.//div[@class="content"]/span/text()').extract_first()
           content = content.strip('\n')#過濾空行

           #將解析到的數據封裝至items對象中
       # 實例化一個item類型的對象
           item = SecondbloodItem()
       # ‘’號中的author是item文件中聲明的屬性名稱
           item['author'] = author
           item['content'] = content

           yield item #提交item對象到管道文件(pipelines.py)

- items文件:items.py

import scrapy


class SecondbloodItem(scrapy.Item):
   # define the fields for your item here like:
   # name = scrapy.Field()
   author = scrapy.Field() #存儲做者(實例化須要存儲的對象)
   content = scrapy.Field() #存儲段子內容

- 管道文件:pipelines.py

# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html

​# 管道文件:須要接受爬蟲文件提交過來的數據,並對數據進行持久化存儲(IO操做)
class SecondbloodPipeline(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('爬蟲結束')

  注意:默認管道機制並無開啓,須要在配置文件中手動開啓。

- 配置文件:settings.py

#開啓管道
ITEM_PIPELINES = {
   'secondblood.pipelines.SecondbloodPipeline': 300, #300表示爲優先級,值越小優先級越高
}
5. 基於mysql的管道存儲

在管道文件裏將item對象中的數據值存儲到了磁盤中,若是將item數據寫入mysql數據庫的話,只須要將上述案例中的管道文件修改爲以下形式:

- pipelines.py文件

# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html

#導入數據庫的類
import pymysql
class QiubaiproPipelineByMysql(object):

   conn = None  #mysql的鏈接對象聲明
   cursor = None#mysql遊標對象聲明
   def open_spider(self,spider):
       print('開始爬蟲')
       #連接數據庫
       self.conn = pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='123456',db='qiubai')
   #編寫向數據庫中存儲數據的相關代碼
   def process_item(self, item, spider):
       #1.連接數據庫
       #2.執行sql語句
       sql = 'insert into qiubai values("%s","%s")'%(item['author'],item['content'])
       self.cursor = self.conn.cursor()
       #執行事務
       try:
           self.cursor.execute(sql)
           self.conn.commit()
       except Exception as e:
           print(e)
           self.conn.rollback()

       return item
   def close_spider(self,spider):
       print('爬蟲結束')
       self.cursor.close()
       self.conn.close()

settings.py

ITEM_PIPELINES = {
   'qiubaiPro.pipelines.QiubaiproPipelineByMysql': 300,
}

 

6. 基於redis的管道存儲

在管道文件裏將item對象中的數據值存儲到了磁盤中,若是將item數據寫入redis數據庫的話,只須要將上述案例中的管道文件修改爲以下形式:

# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html

import redis

class QiubaiproPipelineByRedis(object):
   conn = None
   def open_spider(self,spider):
       print('開始爬蟲')
       #建立連接對象
       self.conn = redis.Redis(host='127.0.0.1',port=6379)
   def process_item(self, item, spider):
       dict = {
           'author':item['author'],
           'content':item['content']
      }
       #寫入redis中
       self.conn.lpush('data', dict)
       return item

- pipelines.py文件

ITEM_PIPELINES = {
   'qiubaiPro.pipelines.QiubaiproPipelineByRedis': 300,
}

面試題:若是最終須要將爬取到的數據值一份存儲到磁盤文件,一份存儲到數據庫中,則應該如何操做scrapy?  

- 答:管道文件中的代碼爲

#該類爲管道類,該類中的process_item方法是用來實現持久化存儲操做的。
class DoublekillPipeline(object):

   def process_item(self, item, spider):
       #持久化操做代碼 (方式1:寫入磁盤文件)
       return item

#若是想實現另外一種形式的持久化操做,則能夠再定製一個管道類:
class DoublekillPipeline_db(object):

   def process_item(self, item, spider):
       #持久化操做代碼 (方式1:寫入數據庫)
       return item

在settings.py開啓管道操做代碼爲:

#下列結構爲字典,字典中的鍵值表示的是即將被啓用執行的管道文件和其執行的優先級。
ITEM_PIPELINES = {
  'doublekill.pipelines.DoublekillPipeline': 300,
   'doublekill.pipelines.DoublekillPipeline_db': 200,
}

#上述代碼中,字典中的兩組鍵值分別表示會執行管道文件中對應的兩個管道類中的process_item方法,實現兩種不一樣形式的持久化操做。

 

7. 遞歸爬取解析多頁頁面數據

- 需求:將糗事百科全部頁碼的做者和段子內容數據進行爬取切持久化存儲

- 需求分析:每個頁面對應一個url,則scrapy工程須要對每個頁碼對應的url依次發起請求,而後經過對應的解析方法進行做者和段子內容的解析。

實現方案:

1.將每個頁碼對應的url存放到爬蟲文件的起始url列表(start_urls)中。(不推薦)

2.使用Request方法手動發起請求。(推薦)

代碼展現:

# -*- coding: utf-8 -*-
import scrapy
from qiushibaike.items import QiushibaikeItem
# scrapy.http import Request
class QiushiSpider(scrapy.Spider):
   name = 'qiushi'
   allowed_domains = ['www.qiushibaike.com']
   start_urls = ['https://www.qiushibaike.com/text/']

   #爬取多頁
   pageNum = 1 #起始頁碼
   url = 'https://www.qiushibaike.com/text/page/%s/' #每頁的url

   def parse(self, response):
       div_list=response.xpath('//*[@id="content-left"]/div')
       for div in div_list:
           #//*[@id="qiushi_tag_120996995"]/div[1]/a[2]/h2
           author=div.xpath('.//div[@class="author clearfix"]//h2/text()').extract_first()
           author=author.strip('\n')
           content=div.xpath('.//div[@class="content"]/span/text()').extract_first()
           content=content.strip('\n')
           item=QiushibaikeItem()
           item['author']=author
           item['content']=content

           yield item #提交item到管道進行持久化

        #爬取全部頁碼數據
       if self.pageNum <= 13: #一共爬取13頁(共13頁)
           self.pageNum += 1
           url = format(self.url % self.pageNum)

           #遞歸爬取數據:callback表示指定的解析方法。遞歸調用parse函數
(將url請求後,獲得的相應數據繼續進行parse解析)。
            yield scrapy.Request(url=url,callback=self.parse)

8. 五大核心組件工做流程:  

  • 引擎(Scrapy Engine) 用來處理整個系統的數據流處理, 觸發事務(框架核心)

  • 調度器(Scheduler) 用來接受引擎發過來的請求, 壓入隊列中, 並在引擎再次請求的時候返回. 能夠想像成一個URL(抓取網頁的網址或者說是連接)的優先隊列, 由它來決定下一個要抓取的網址是什麼, 同時去除重複的網址

  • 下載器(Downloader) 用於下載網頁內容, 並將網頁內容返回給蜘蛛(Scrapy下載器是創建在twisted這個高效的異步模型上的)

  • 爬蟲(Spiders) 爬蟲是主要幹活的, 用於從特定的網頁中提取本身須要的信息, 即所謂的實體(Item)。用戶也能夠從中提取出連接,讓Scrapy繼續抓取下一個頁面

  • 項目管道(Pipeline) 負責處理爬蟲從網頁中抽取的實體,主要的功能是持久化實體、驗證明體的有效性、清除不須要的信息。當頁面被爬蟲解析後,將被髮送到項目管道,並通過幾個特定的次序處理數據。

  數據爬取流程:spiders文件擁有爬取的url地址,並封裝成請求對象,傳遞給引擎,引擎接着傳遞給調度器,調度器會把傳遞來的請求對象中的請求隊列內的每一條請求,經過中間件傳遞給下載器,進行數據下載,並把數據封裝到響應對象中,而後再傳遞給引擎,引擎接着把響應對象傳遞給spiders文件,進行解析。若爬取的是動態加載的數據,則須要把下載器經過中間件傳遞給引擎的響應對象攔截,在中間件中從新實例化一個響應對象,該實例化對象中包含動態的頁面數據。

簡單反爬手段:

  UA假裝 :

    UA即 User-Agent 表示用戶代理,其實就是用戶身份。當瀏覽器隨便訪問一個網站時,經過請求的Headers中查看Request Headers的User-Agent,能夠看到後面有瀏覽器的名字,這就表明用戶身份是瀏覽器。若直接以爬蟲的身份訪問,就會受到很大限制,那麼經過修改UA假裝成瀏覽器,就是UA假裝了。

9. post請求發送

- 問題:在以前代碼中,咱們歷來沒有手動的對start_urls列表中存儲的起始url進行過請求的發送,可是起始url的確是進行了請求的發送,那這是如何實現的呢?

- 解答:實際上是由於爬蟲文件中的爬蟲類繼承到了Spider父類中的start_requests(self)這個方法,該方法就能夠對start_urls列表中的url發起請求:

  def start_requests(self):
       for u in self.start_urls:
          yield scrapy.Request(url=u,callback=self.parse)

【注意】該方法默認的實現,是對起始的url發起get請求,若是想發起post請求,則須要子類重寫該方法。

  -方法: 重寫start_requests方法,讓其發起post請求:

def start_requests(self):
       #請求的url
       post_url = 'http://fanyi.baidu.com/sug'
       # post請求參數
       formdata = {
           'kw': 'wolf',
      }
       # 發送post請求
       yield scrapy.FormRequest(url=post_url, formdata=formdata, callback=self.parse)

 

10. Scrapy的日誌等級

  - 在使用scrapy crawl spiderFileName運行程序時,在終端裏打印輸出的就是scrapy的日誌信息。

  - 日誌信息的種類:

        ERROR : 通常錯誤

        WARNING : 警告

        INFO : 通常的信息

        DEBUG : 調試信息

       

  - 設置日誌信息指定輸出:

    在settings.py配置文件中,加入

LOG_LEVEL = ‘指定日誌信息種類’便可。

LOG_FILE = 'log.txt'則表示將日誌信息寫入到指定文件中進行存儲。

11. 請求傳參

  - 在某些狀況下,咱們爬取的數據不在同一個頁面中,例如,咱們爬取一個電影網站,電影的名稱,評分在一級頁面,而要爬取的其餘電影詳情在其二級子頁面中。這時咱們就須要用到請求傳參。

  - 案例展現:爬取www.id97.com電影網,將一級頁面中的電影名稱,類型,評分一級二級頁面中的上映時間,導演,片長進行爬取。

  爬蟲文件:

# -*- coding: utf-8 -*-
import scrapy
from moviePro.items import MovieproItem

class MovieSpider(scrapy.Spider):
   name = 'movie'
   allowed_domains = ['www.id97.com']
   start_urls = ['http://www.id97.com/']

   def parse(self, response):
       div_list = response.xpath('//div[@class="col-xs-1-5 movie-item"]')

       for div in div_list:
           item = MovieproItem()
           item['name'] = div.xpath('.//h1/a/text()').extract_first()
           item['score'] = div.xpath('.//h1/em/text()').extract_first()
           #xpath(string(.))表示提取當前節點下全部子節點中的數據值(.)表示當前節點
           item['kind'] = div.xpath('.//div[@class="otherinfo"]').xpath('string(.)').extract_first()
           item['detail_url'] = div.xpath('./div/a/@href').extract_first()
           #請求二級詳情頁面,解析二級頁面中的相應內容,經過meta參數進行Request的數據傳遞
           yield scrapy.Request(url=item['detail_url'],callback=self.parse_detail,meta={'item':item})

   def parse_detail(self,response):
       #經過response獲取item
       item = response.meta['item']
       item['actor'] = response.xpath('//div[@class="row"]//table/tr[1]/a/text()').extract_first()
       item['time'] = response.xpath('//div[@class="row"]//table/tr[7]/td[2]/text()').extract_first()
       item['long'] = response.xpath('//div[@class="row"]//table/tr[8]/td[2]/text()').extract_first()
       #提交item到管道
       yield item

 items文件:

# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html

import scrapy


class MovieproItem(scrapy.Item):
   # define the fields for your item here like:
   name = scrapy.Field()
   score = scrapy.Field()
   time = scrapy.Field()
   long = scrapy.Field()
   actor = scrapy.Field()
   kind = scrapy.Field()
   detail_url = scrapy.Field()

管道文件:

# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html

import json
class MovieproPipeline(object):
   def __init__(self):
       self.fp = open('data.txt','w')
   def process_item(self, item, spider):
       dic = dict(item)
       print(dic)
       json.dump(dic,self.fp,ensure_ascii=False)
       return item
   def close_spider(self,spider):
       self.fp.close()

12. 如何提升scrapy的爬取效率
增長併發:
  默認scrapy開啓的併發線程爲32個,能夠適當進行增長。在settings配置文件中修改CONCURRENT_REQUESTS = 100值爲100,併發設置成了爲100。

下降日誌級別:
  在運行scrapy時,會有大量日誌信息的輸出,爲了減小CPU的使用率。能夠設置log輸出信息爲INFO或者ERROR便可。在配置文件中編寫:LOG_LEVEL = ‘INFO’

禁止cookie:
  若是不是真的須要cookie,則在scrapy爬取數據時能夠進制cookie從而減小CPU的使用率,提高爬取效率。在配置文件中編寫:COOKIES_ENABLED = False

禁止重試:
  對失敗的HTTP進行從新請求(重試)會減慢爬取速度,所以能夠禁止重試。在配置文件中編寫:RETRY_ENABLED = False

減小下載超時:
  若是對一個很是慢的連接進行爬取,減小下載超時能夠能讓卡住的連接快速被放棄,從而提高效率。在配置文件中進行編寫:DOWNLOAD_TIMEOUT = 10 超時時間爲10s

測試案例:爬取校花網校花圖片 www.521609.com

# -*- coding: utf-8 -*-
import scrapy
from xiaohua.items import XiaohuaItem

class XiahuaSpider(scrapy.Spider):

   name = 'xiaohua'
   allowed_domains = ['www.521609.com']
   start_urls = ['http://www.521609.com/daxuemeinv/']

   pageNum = 1
   url = 'http://www.521609.com/daxuemeinv/list8%d.html'

   def parse(self, response):
       li_list = response.xpath('//div[@class="index_img list_center"]/ul/li')
       for li in li_list:
           school = li.xpath('./a/img/@alt').extract_first()
           img_url = li.xpath('./a/img/@src').extract_first()

           item = XiaohuaItem()
           item['school'] = school
           item['img_url'] = 'http://www.521609.com' + img_url

           yield item

       if self.pageNum < 10:
           self.pageNum += 1
           url = format(self.url % self.pageNum)
           #print(url)
           yield scrapy.Request(url=url,callback=self.parse)

# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html

import scrapy


class XiaohuaItem(scrapy.Item):
   # define the fields for your item here like:
   # name = scrapy.Field()
   school=scrapy.Field()
   img_url=scrapy.Field()

# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html

import json
import os
import urllib.request
class XiaohuaPipeline(object):
   def __init__(self):
       self.fp = None

   def open_spider(self,spider):
       print('開始爬蟲')
       self.fp = open('./xiaohua.txt','w')

   def download_img(self,item):
       url = item['img_url']
       fileName = item['school']+'.jpg'
       if not os.path.exists('./xiaohualib'):
           os.mkdir('./xiaohualib')
       filepath = os.path.join('./xiaohualib',fileName)
       urllib.request.urlretrieve(url,filepath)
       print(fileName+"下載成功")

   def process_item(self, item, spider):
       obj = dict(item)
       json_str = json.dumps(obj,ensure_ascii=False)
       self.fp.write(json_str+'\n')

       #下載圖片
       self.download_img(item)
       return item

   def close_spider(self,spider):
       print('結束爬蟲')
       self.fp.close()

配置文件:

USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36'

# Obey robots.txt rules
ROBOTSTXT_OBEY = False

# Configure maximum concurrent requests performed by Scrapy (default: 16)
CONCURRENT_REQUESTS = 100
COOKIES_ENABLED = False
LOG_LEVEL = 'ERROR'
RETRY_ENABLED = False
DOWNLOAD_TIMEOUT = 3
# Configure a delay for requests for the same website (default: 0)
# See https://doc.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
# The download delay setting will honor only one of:
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
#CONCURRENT_REQUESTS_PER_IP = 16
DOWNLOAD_DELAY = 3

 

13. scrapy中的selenium

未完......

相關文章
相關標籤/搜索