Python中的爬蟲

目錄javascript

爬蟲

啓動:jupyter notebook
介紹:
anaconda是一個集成環境(數據分析+機器學習)
提供了一個叫作jupyter的可視化工具(基於瀏覽器)
jupyter的基本使用
快捷鍵:
插入cell:a,b
刪除:x
執行:shift+enter
切換cell的模式:y,m
tab:自動補全
打開幫助文檔:shift+tab

1. 什麼是爬蟲 :

  • 經過編寫程序模擬瀏覽器上網,從互聯網中爬取須要的數據的過程

2. 爬蟲的分類 :

  • 通用爬蟲 : 爬取一整張頁面源碼數據.搜索引擎 (抓取系統→內部封好的一套爬蟲程序) 重點使用的是該種形式爬蟲
  • 聚焦爬蟲 : 抓取頁面中指定的局部數據
  • 增量式爬蟲 : 監測網站數據更新的狀況.抓取網站最新更新的數據

3. 爬蟲安全性的探究

  • 風險所在php

    • 爬蟲干擾了被訪問網站的正常運營;
    • 爬蟲抓取了受到法律保護的特定類型的數據或信息
  • 如何規避風險css

    • 嚴格遵照網站設置的robots協議;
    • 在規避反爬蟲措施的同時,須要優化本身的代碼,避免干擾被訪問網站的正常運行;
    • 在使用、傳播抓取到的信息時,應審查所抓取的內容,如發現屬於用戶的我的信息、隱私或者他人的商業祕密的,應及時中止並刪除

    爬蟲機制 :應用在網站中html

    反反爬機制 : 應用在爬蟲程序中java

    第一個反爬機制 :node

    ​ robots協議:純文本的協議python

    • 特色:防君子不防小人

4. http & https

  • 什麼是http協議
    • 服務器和客戶端進行數據交互的某種形式
  • https - 安全 (數據加密) 的http協議

頭部信息

一、通用頭部

通用頭域包含請求和響應消息都支持的頭域。mysql

Request URL:請求的URL地址
Request Method: 請求方法,get/post/put/……
Status Code:狀態碼,200 爲請求成功
Remote Address:路由地址

二、請求頭部

1) Accept:  告訴WEB服務器本身接受什麼介質類型,*/* 表示任何類型,type/* 表示該類型下的全部子類型;
2)Accept-Charset:  瀏覽器申明本身接收的字符集
  Accept-Encoding:瀏覽器申明本身接收的編碼方法,一般指定壓縮方法,是否支持壓縮,支持什麼壓縮方法(gzip,     deflate)
3)Accept-Language:  瀏覽器申明本身接收的語言。語言跟字符集的區別:中文是語言,中文有多種字符集,好比big5,gb2312,gbk等等。
4)Authorization:  當客戶端接收到來自WEB服務器的 WWW-Authenticate 響應時,該頭部來回應本身的身份驗證信息給WEB服務器。
5)Connection:表示是否須要持久鏈接。close(告訴WEB服務器或者代理服務器,在完成本次請求的響應後,斷開鏈接,
     不要等待本次鏈接的後續請求了)。keep-alive(告訴WEB服務器或者代理服務器,在完成本次請求的響應後,保持鏈接,等待本次鏈接的後續請求)。
6)Referer:發送請求頁面URL。瀏覽器向 WEB 服務器代表本身是從哪一個 網頁/URL 得到/點擊 當前請求中的網址/URL。
7)User-Agent: 瀏覽器代表本身的身份(是哪一種瀏覽器)。
8)Host: 發送請求頁面所在域。
9)Cache-Control:瀏覽器應遵循的緩存機制。
       no-cache(不要緩存的實體,要求如今從WEB服務器去取)
       max-age:(只接受 Age 值小於 max-age 值,而且沒有過時的對象) 
       max-stale:(能夠接受過去的對象,可是過時時間必須小於 max-stale 值)  
       min-fresh:(接受其新鮮生命期大於其當前 Age 跟 min-fresh 值之和的緩存對象)
10)Pramga:主要使用 Pramga: no-cache,至關於 Cache-Control: no-cache。
11)Range:瀏覽器(好比 Flashget 多線程下載時)告訴 WEB 服務器本身想取對象的哪部分。
12)Form:一種請求頭標,給定控制用戶代理的人工用戶的電子郵件地址。
13)Cookie:這是最重要的請求頭信息之一

三、響應頭部

1)Age:當代理服務器用本身緩存的實體去響應請求時,用該頭部代表該實體從產生到如今通過多長時間了。
2)Accept-Ranges:WEB服務器代表本身是否接受獲取其某個實體的一部分(好比文件的一部分)的請求。bytes:表示接受,none:表示不接受。
3) Cache-Control:服務器應遵循的緩存機制。
    public(能夠用 Cached 內容迴應任何用戶)
    private(只能用緩存內容迴應先前請求該內容的那個用戶)
    no-cache(能夠緩存,可是隻有在跟WEB服務器驗證了其有效後,才能返回給客戶端) 
    max-age:(本響應包含的對象的過時時間)  
    ALL:  no-store(不容許緩存)  
4) Connection: 是否須要持久鏈接
        close(鏈接已經關閉)。
        keepalive(鏈接保持着,在等待本次鏈接的後續請求)。
        Keep-Alive:若是瀏覽器請求保持鏈接,則該頭部代表但願 WEB 服務器保持鏈接多長時間(秒)。例如:Keep-                     Alive:300
5)Content-Encoding:WEB服務器代表本身使用了什麼壓縮方法(gzip,deflate)壓縮響應中的對象。 例如:Content-Encoding:gzip 
6)Content-Language:WEB 服務器告訴瀏覽器本身響應的對象的語言。
7)Content-Length:WEB 服務器告訴瀏覽器本身響應的對象的長度。例如:Content-Length: 26012
8)Content-Range:WEB 服務器代表該響應包含的部分對象爲整個對象的哪一個部分。例如:Content-Range: bytes 21010-47021/47022
9)Content-Type:WEB 服務器告訴瀏覽器本身響應的對象的類型。例如:Content-Type:application/xml
10)Expired:WEB服務器代表該實體將在何時過時,對於過時了的對象,只有在跟WEB服務器驗證了其有效性後,才能用來響應客戶請求。
11) Last-Modified:WEB 服務器認爲對象的最後修改時間,好比文件的最後修改時間,動態頁面的最後產生時間等等。
12) Location:WEB 服務器告訴瀏覽器,試圖訪問的對象已經被移到別的位置了,到該頭部指定的位置去取。
13)Proxy-Authenticate: 代理服務器響應瀏覽器,要求其提供代理身份驗證信息。
14)Server: WEB 服務器代表本身是什麼軟件及版本等信息。
15)Refresh:表示瀏覽器應該在多少時間以後刷新文檔,以秒計。

https的加密方式

對稱密鑰加密
非對稱密鑰加密
證書密鑰加密

5. request模塊

基於網絡請求的python模塊jquery

做用 :模擬瀏覽器發送請求,實現爬蟲linux

環境安裝 : pip install request

編碼流程 :

  • 指定url
  • 發起請求
  • 獲取響應數據
  • 持久化存儲

1. 爬取搜狗首頁的頁面源碼數據

import requests
#1.指定url
url = 'https://www.sogou.com/'
#2.請求發送:get返回的是一個響應對象
response = requests.get(url=url)
#3.獲取響應數據:text返回的是字符串形式的響應數據
page_text = response.text
#4.持久化存儲
with open('./sogou.html','w',encoding='utf-8') as fp:
    fp.write(page_text)

2. 實現一個簡易的網頁採集器

請求參數的動態化

url = 'https://www.sogou.com/web'
#請求參數的動態化
wd = input('enter a key word:')
params = {
    'query':wd
}
response = requests.get(url=url,params=params)
page_text = response.text
fileName = wd+'.html'
with open(fileName,'w',encoding='utf-8') as fp:
    fp.write(page_text)
print(fileName,'爬取成功!')

上述代碼問題:

  • 亂碼問題
    • response.encoding = 'xxx'
  • 數據丟失
    • 反爬機制:UA檢測
    • 反反爬策略:UA假裝
#亂碼問題的解決
url = 'https://www.sogou.com/web'
#請求參數的動態化
wd = input('enter a key word:')
params = {
    'query':wd
}

response = requests.get(url=url,params=params)

#將響應數據的編碼格式手動進行指定
response.encoding = 'utf-8'
page_text = response.text
fileName = wd+'.html'
with open(fileName,'w',encoding='utf-8') as fp:
    fp.write(page_text)
print(fileName,'爬取成功!')
#UA假裝操做
url = 'https://www.sogou.com/web'
#請求參數的動態化
wd = input('enter a key word:')
params = {
    'query':wd
}

#UA假裝
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'
}
response = requests.get(url=url,params=params,headers=headers)

#將響應數據的編碼格式手動進行指定
response.encoding = 'utf-8'
page_text = response.text
fileName = wd+'.html'
with open(fileName,'w',encoding='utf-8') as fp:
    fp.write(page_text)
print(fileName,'爬取成功!')

3. 動態加載的數據

經過另外一個網絡請求 (ajax) 請求到的數據

爬取豆瓣電影中動態加載出的電影詳情數據 :

url = 'https://movie.douban.com/j/chart/top_list'
#參數動態化
params = {
    'type': '17',
    'interval_id': '100:90',
    'action': '',
    'start': '0',
    'limit': '200',
}
response = requests.get(url=url,params=params,headers=headers)
#json()返回的是序列化好的對象
movie_list = response.json()
for movie in movie_list:
    print(movie['title'],movie['score'])
總結:對一個陌生的網站進行數據爬取的時候,首先肯定的一點就是爬取的數據是否爲動態加載出來的
    是:須要經過抓包工具捕獲到動態加載數據對應的數據包,從中提取出url和請求參數。
    不是:直接對瀏覽器地址欄的url發起請求便可
如何檢測爬取的數據是否是動態加載出來的?
    經過抓包工具進行局部搜索就能夠驗證數據是否爲動態加載
        搜索到:不是動態加載
        搜索不到:是動態加載
如何定位動態加載的數據在哪呢?
    經過抓包工具進行全局搜索進行定位

4. 爬取肯德基的餐廳位置信息

http://www.kfc.com.cn/kfccda/storelist/index.aspx

url = 'http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword'
data = {
    'cname': '',
    'pid': '',
    'keyword': '上海',
    'pageIndex': '1',
    'pageSize': '10',
}
address_dic = requests.post(url=url,data=data,headers=headers).json()
for dic in address_dic['Table1']:
    print(dic['addressDetail'])

5. 面試題

- 需求
https://www.fjggfw.gov.cn/Website/JYXXNew.aspx 福建省公共資源交易中心
提取內容:
工程建設中的中標結果信息/中標候選人信息
1. 完整的html中標信息
2. 第一中標候選人
3. 中標金額
4. 中標時間
5. 其它參與投標的公司
- 實現思路
    - 確認爬取的數據都是動態加載出來的
    - 在首頁中捕獲到ajax請求對應的數據包,從該數據包中提取出請求的url和請求參數
    - 對提取到的url進行請求發送,獲取響應數據(json)
    - 從json串中提取到每個公告對應的id值
    - 將id值和中標信息對應的url進行整合,進行請求發送捕獲到每個公告對應的中標信息數據
post_url = 'https://www.fjggfw.gov.cn/Website/AjaxHandler/BuilderHandler.ashx'
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36',
    'Cookie': '_qddac=4-3-1.4euvh3.x4wulp.k1hj8mnw; ASP.NET_SessionId=o4xkycpib3ry5rzkvfcamxzk; Hm_lvt_94bfa5b89a33cebfead2f88d38657023=1570520304; __root_domain_v=.fjggfw.gov.cn; _qddaz=QD.89mfu7.7kgq8w.k1hj8mhg; _qdda=4-1.4euvh3; _qddab=4-x4wulp.k1hj8mnw; _qddamta_2852155767=4-0; _qddagsx_02095bad0b=2882f90558bd014d97adf2d81c54875229141367446ccfed2b0c8913707c606ccf30ec99a338fed545821a5ff0476fd6332b8721c380e9dfb75dcc00600350b31d85d17d284bb5d6713a887ee73fa35c32b7350c9909379a8d9f728ac0c902e470cb5894c901c4176ada8a81e2ae1a7348ae5da6ff97dfb43a23c6c46ec8ec10; Hm_lpvt_94bfa5b89a33cebfead2f88d38657023=1570520973'
}
data = {
    'OPtype': 'GetListNew',
    'pageNo': '1',
    'pageSize': '10',
    'proArea': '-1',
    'category': 'GCJS',
    'announcementType': '-1',
    'ProType': '-1',
    'xmlx': '-1',
    'projectName': '',
    'TopTime': '2019-07-10 00:00:00',
    'EndTime': '2019-10-08 23:59:59',
    'rrr': '0.7293828344656237',
}
post_data = requests.post(url=post_url,headers=headers,data=data).json()
for dic in post_data['data']:
    _id = int(dic['M_ID'])
    detail_url = 'https://www.fjggfw.gov.cn/Website/AjaxHandler/BuilderHandler.ashx?OPtype=GetGGInfoPC&ID={}&GGTYPE=5&url=AjaxHandler%2FBuilderHandler.ashx'.format(_id)
    company_data = requests.get(url=detail_url,headers=headers).json()['data']
    company_str = ''.join(company_data)
    print(company_str)

6. 數據解析

1. 如何爬取圖片數據?

- 基於requests|
- 基於urllib
- 區別:urllib中的urlretrieve不能夠進行UA假裝
import requests
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'
}
#基於requests的圖片爬取
url = 'http://tva1.sinaimg.cn/mw600/007QUzsKgy1g7qzr59hk7j30cs0gxn82.jpg'
img_data = requests.get(url=url,headers=headers).content #content返回的是byte類型的響應數據
with open('./123.jpg','wb') as fp:
    fp.write(img_data)
#基於urllib的圖片爬取
from urllib import request
url = 'http://tva1.sinaimg.cn/mw600/007QUzsKgy1g7qzr59hk7j30cs0gxn82.jpg'
request.urlretrieve(url,'./456.jpg')

2. 數據解析

數據解析

  • 概念:將一整張頁面中的局部數據進行提取/解析

  • 做用:用來實現聚焦爬蟲的吧

  • 實現方式:

    • 正則
    • bs4
    • xpath
    • pyquery
  • 數據解析的通用原理是什麼?

    • 標籤的定位
    • 數據的提取
  • 頁面中的相關的字符串的數據都存儲在哪裏呢?

    • 標籤中間
    • 標籤的屬性中
  • - 基於聚焦爬蟲的編碼流程
        - 指定url
        - 發起請求
        - 獲取響應數據
        - 數據解析
        - 持久化存儲

正則解析

- 將煎蛋網中的圖片數據進行爬取且存儲在本地 :

import re
import os

dirName = './imgLibs'
if not os.path.exists(dirName):
    os.mkdir(dirName)
    
url = 'http://jandan.net/pic/MjAxOTEwMDktNjY=#comments'
page_text = requests.get(url,headers=headers).text
#解析數據:img標籤的src的屬性值
ex = '<div class="text">.*?<img src="(.*?)" referrerPolicy.*?</div>'
img_src_list = re.findall(ex,page_text,re.S)
for src in img_src_list:
    if 'org_src' in src:
        src = re.findall('org_src="(.*?)" onload',src)[0]
    src = 'http:'+src
    imgName = src.split('/')[-1]
    imgPath = dirName+'/'+imgName
    request.urlretrieve(src,imgPath)
    print(imgName,'下載成功!!!')

bs4解析

  • - 環境的安裝:
      - pip install bs4
      - pip install lxml
    - bs4的解析原理:
      - 實例化一個BeautifulSoup的一個對象,把即將被解析的頁面源碼數據加載到該對象中
      - 須要調用BeautifulSoup對象中的相關的方法和屬性進行標籤訂位和數據的提取
    - BeautifulSoup的實例化
      - BeautifulSoup(fp,'lxml'):將本地存儲的html文檔中的頁面源碼數據加載到該對象中
      - BeautifulSoup(page_text,'lxml'):將從互聯網中請求道的頁面源碼數據加載到改對象中
    - 標籤的定位
      - soup.tagName:只能夠定位到第一個tagName標籤
      - 屬性定位:soup.find('tagName',attrName='value'),只能夠定位到符合要求的第一個標籤
        - findAll:返回值是一個列表。能夠定位到符合要求的全部標籤
      - 選擇器定位:soup.select('選擇器')
        - 選擇器:id,class,tag,層級選擇器(大於號表示一個層級,空格表示多個層級)
    - 取文本
      - text:將標籤中全部的文本取出
      - string:將標籤中直系的文本取出
    - 取屬性
      - tag['attrName']
from bs4 import BeautifulSoup
fp = open('./test.html',encoding='utf-8')
soup = BeautifulSoup(fp,'lxml')
# soup.div
# soup.find('div',class_='song')
# soup.findAll('div',class_='song')
# soup.select('#feng')[0]
# soup.select('.tang > ul > li > a')
# soup.select('.tang a')
# tag = soup.b
# tag.string
# div_tag = soup.find('div',class_='tang')
# div_tag.text
a_tag = soup.select('#feng')[0]
a_tag

- 使用bs4解析三國演義小說的標題和內容,存儲到本地 :

main_url = 'http://www.shicimingju.com/book/sanguoyanyi.html'
page_text = requests.get(url=main_url,headers=headers).text
#數據解析:章節的標題和詳情頁的url
soup = BeautifulSoup(page_text,'lxml')
a_list = soup.select('.book-mulu > ul > li > a')
fp = open('./sanguo.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
    #數據解析:章節內容
    detail_soup = BeautifulSoup(detail_page_text,'lxml')
    div_tag = detail_soup.find('div',class_='chapter_content')
    content = div_tag.text
    
    fp.write(title+':'+content+'\n')
    print(title,'寫入成功!!!')
fp.close()

xpath解析

  • - 環境的安裝
      - pip install lxml
    - 解析原理
      - 實例化一個etree的對象,且把即將被解析的頁面源碼數據加載到該對象中
      - 調用etree對象中的xpath方法結合這不一樣形式的xpath表達式進行標籤訂位和數據提取
    - etree對象的實例化
      - etree.parse('fileName')  - 本地文檔
      - etree.HTML(page_text) - 網絡請求
    - 標籤訂位
      - 最左側的/:必定要從根標籤開始進行標籤訂位
      - 非最左側的/:表示一個層級
      - 最左側的//:能夠從任意位置進行指定標籤的定位
      - 非最左側的//:表示多個層級
      - 屬性定位://tagName[@attrName="value"]
      - 索引定位://tagName[@attrName="value"]/li[2],索引是從1開始
      - 邏輯運算:
        - 找到href屬性值爲空且class屬性值爲du的a標籤
        - //a[@href="" and @class="du"]
      - 模糊匹配:
        - //div[contains(@class, "ng")]
        - //div[starts-with(@class, "ta")]
    - 取文本
      - /text():直系的文本內容
      - //text():全部的文本內容
    - 取屬性
      - /@attrName
from lxml import etree
tree = etree.parse('./test.html')
# tree.xpath('/html//title')
# tree.xpath('//div')
# tree.xpath('//div[@class="tang"]')
# tree.xpath('//div[@class="tang"]/ul/li[2]')
# tree.xpath('//p[1]/text()')
# tree.xpath('//div[@class="song"]//text()')
tree.xpath('//img/@src')[0]
  • 需求:爬取虎牙主播名稱,熱度和標題
url = 'https://www.huya.com/g/xingxiu'
page_text = requests.get(url=url,headers=headers).text

#數據解析
tree = etree.HTML(page_text)
li_list = tree.xpath('//div[@class="box-bd"]/ul/li')
for li in li_list:
    #實現的是頁面局部數據的指定數據的解析
    title = li.xpath('./a[2]/text()')[0]
    author = li.xpath('./span/span[1]/i/text()')[0]
    hot = li.xpath('./span/span[2]/i[2]/text()')[0]
    
    print(title,author,hot)
  • 爬取http://pic.netbian.com/4kmeinv/中前五頁的圖片數據
    • 中文亂碼的處理
    • 多頁碼數據的爬取
# url = 'http://pic.netbian.com/4kmeinv/' #第一頁
#指定一個通用的url模板:不可變的
url = 'http://pic.netbian.com/4kmeinv/index_%d.html'
dirName = './MZLib'
if not os.path.exists(dirName):
    os.mkdir(dirName)
    
for page in range(1,6):
    if page == 1:
        new_url = 'http://pic.netbian.com/4kmeinv/'
    else:
        new_url = format(url%page)
    page_text = requests.get(url=new_url,headers=headers).text
    #數據解析:圖片地址&圖片名稱
    tree = etree.HTML(page_text)
    li_list = tree.xpath('//div[@class="slist"]/ul/li')
    for li in li_list:
        img_name = li.xpath('./a/img/@alt')[0]
        img_name = img_name.encode('iso-8859-1').decode('gbk')+'.jpg'
        img_src = 'http://pic.netbian.com'+li.xpath('./a/img/@src')[0]
        img_data = requests.get(img_src,headers=headers).content #圖片的二進制類型數據
        img_path = dirName+'/'+img_name
        with open(img_path,'wb') as fp:
            fp.write(img_data)
    print('第{}頁爬取完畢!!!'.format(page))
  • 爬取全國城市的名稱
    • https://www.aqistudy.cn/historydata/
url = 'https://www.aqistudy.cn/historydata/'
page_text = requests.get(url,headers=headers).text
tree = etree.HTML(page_text)
# hot_cities = tree.xpath('//div[@class="bottom"]/ul/li/a/text()')
# all_cities = tree.xpath('//div[@class="bottom"]/ul/div[2]/li/a/text()')
tree.xpath('//div[@class="bottom"]/ul/div[2]/li/a/text() | //div[@class="bottom"]/ul/li/a/text()')

7. 代理

代理指的就是代理服務器
代理的做用 : 
    請求和響應數據的轉發
代理和爬蟲之間的關聯 :
    能夠基於代理實現更換爬蟲程序請求的ip地址
代理網站 :
    1. 西祠 https://www.xicidaili.com/nn/
    2. 快代理
    3. www.goubanjia.comm
    4. 代理精靈 http://http.zhiliandaili.cn/
代理的匿名度 :
    高匿 : 所訪問的服務器察覺不到是不是代理訪問,也沒法知曉真正訪問的ip
    匿名 : 所訪問的服務器知道是代理訪問,但沒法查到真正的ip
    透明 : 知道是代理,而且知道真實ip
類型 :
    http
    https
# 使用代理髮請求 
import requests
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36',
    'Connection':'close'
}
url = 'https://www.baidu.com/s?ie=UTF-8&wd=ip'
page_text = requests.get(url,headers=headers,proxies={'https':'125.87.99.237:22007'}).text
with open('./ip.html','w',encoding='utf-8') as fp:
    fp.write(page_text)
  • 搭建一個免費的代理池 (利用付費代理ip爬取免費代理網站的ip)
#構建一個付費的代理池
import random
ips_pool = []
url = 'http://ip.11jsq.com/index.php/api/entry?method=proxyServer.generate_api_url&packid=1&fa=0&fetch_key=&groupid=0&qty=103&time=1&pro=&city=&port=1&format=html&ss=5&css=&dt=1&specialTxt=3&specialJson=&usertype=2'
page_text = requests.get(url,headers=headers).text
tree = etree.HTML(page_text)
ip_list = tree.xpath('//body//text()')
for ip in ip_list:
    dic = {'https':ip}
    ips_pool.append(dic)

from lxml import etree
url = 'https://www.xicidaili.com/nn/%d' #通用的url模板(不可變)
all_ips = []
for page in range(1,5):
    new_url = format(url%page)
    page_text = requests.get(new_url,headers=headers,proxies=random.choice(ips_pool)).text
    tree = etree.HTML(page_text)
    #在xpath表達式中不能夠出現tbody標籤
    tr_list = tree.xpath('//*[@id="ip_list"]//tr')[1:]
    for tr in tr_list:
        ip = tr.xpath('./td[2]/text()')[0]
        port = tr.xpath('./td[3]/text()')[0]
        type_ip = tr.xpath('./td[6]/text()')[0]
        dic = {
            'ip':ip,
            'port':port,
            'type':type_ip
        }
        all_ips.append(dic)
                
print(len(all_ips))

8. cookie

需求:將https://xueqiu.com/中的新聞數據進行爬取
爬蟲中處理cookie的操做
    手動處理:將cookie寫在headers中
    自動處理:session對象。
獲取session對象:requests.Session()
做用:
    session對象和requests對象均可以對指定的url進行請求發送。只不過使用session進行請求發送的過程當中若是產生了cookie則cookie會被自動存儲在session對象中
url = 'https://xueqiu.com/v4/statuses/public_timeline_by_category.json?since_id=-1&max_id=20352188&count=15&category=-1'

news_json = requests.get(url,headers=headers).json()
news_json

#基於cookie操做的修正
session = requests.Session()
url = 'https://xueqiu.com/v4/statuses/public_timeline_by_category.json?since_id=-1&max_id=20352188&count=15&category=-1'
#將cookie存儲到session中,目的是將cookie獲取存儲到session中
session.get('https://xueqiu.com/',headers=headers) 

#保證該次請求時攜帶對應的cookie才能夠請求成功
news_json = session.get(url,headers=headers).json()
news_json

9. 驗證碼的識別

使用線上的打碼平臺進行自動的識別:
    - 雲打碼
    - 超級鷹 :
        - 註冊《用戶中心》身份的帳戶
        - 登錄
            - 建立一個軟件
            - 下載示例代碼《開發文檔》
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
        self.base_params = {
            'user': self.username,
            'pass2': self.password,
            'softid': self.soft_id,
        }
        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()
    
chaojiying = Chaojiying_Client('bobo328410948', 'bobo328410948', '899370')  #用戶中心>>軟件ID 生成一個替換 96001
im = open('a.jpg', 'rb').read()                                                 #本地圖片文件路徑 來替換 a.jpg 有時WIN系統需要//
print(chaojiying.PostPic(im,1004)['pic_str'])   




#驗證碼識別函數的封裝
def transformCode(imgPath,imgType):
    chaojiying = Chaojiying_Client('bobo328410948', 'bobo328410948', '899370')
    im = open(imgPath, 'rb').read()
    return chaojiying.PostPic(im,imgType)['pic_str']

模擬登錄

版本一 :

版本一的問題 :

請求須要有動態的參數

一般請狀況下動態變化的請求參數都會被隱藏在前臺頁面源碼中

from urllib import request

#驗證碼的識別:將驗證碼下載到本地而後提交給打嗎平臺進行識別
main_url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx'
page_text = requests.get(main_url,headers=headers).text
tree = etree.HTML(page_text)
code_src = 'https://so.gushiwen.org'+tree.xpath('//*[@id="imgCode"]/@src')[0]
request.urlretrieve(code_src,'./code.jpg')

#識別驗證碼
code_text = transformCode('./code.jpg',1004)


login_url = 'https://so.gushiwen.org/user/login.aspx?from=http%3a%2f%2fso.gushiwen.org%2fuser%2fcollect.aspx'
data = {
    '__VIEWSTATE': '8/BKAQBaZHn7+GP+Kl2Gx43fFO1NI32RMyVae0RyrtFQue3IAhzQKvkml41cIT42Y//OcQccA8AqGYkvB+NFkU43uaHqU69Y0Z1WT3ZRrr4vR+CF7JlBG29POXM=',
    '__VIEWSTATEGENERATOR': 'C93BE1AE',
    'from': 'http://so.gushiwen.org/user/collect.aspx',
    'email': 'www.zhangbowudi@qq.com',
    'pwd': 'bobo328410948',
    'code': code_text,
    'denglu': '登陸',
}
print(code_text)
page_text = requests.post(login_url,headers=headers,data=data).text

with open('./login.html','w',encoding='utf-8') as fp:
    fp.write(page_text)

版本二 :

版本二遇到的問題 :

​ 沒有攜帶cookie ,且這個網站的cookie在驗證碼的請求裏

#驗證碼的識別:將驗證碼下載到本地而後提交給打嗎平臺進行識別
main_url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx'
page_text = requests.get(main_url,headers=headers).text
tree = etree.HTML(page_text)
code_src = 'https://so.gushiwen.org'+tree.xpath('//*[@id="imgCode"]/@src')[0]
request.urlretrieve(code_src,'./code.jpg')

#解析出動態變化的請求參數
__VIEWSTATE = tree.xpath('//*[@id="__VIEWSTATE"]/@value')[0]
__VIEWSTATEGENERATOR = tree.xpath('//*[@id="__VIEWSTATEGENERATOR"]/@value')[0]

#識別驗證碼
code_text = transformCode('./code.jpg',1004)


login_url = 'https://so.gushiwen.org/user/login.aspx?from=http%3a%2f%2fso.gushiwen.org%2fuser%2fcollect.aspx'
data = {
    '__VIEWSTATE': __VIEWSTATE,
    '__VIEWSTATEGENERATOR': __VIEWSTATEGENERATOR,
    'from': 'http://so.gushiwen.org/user/collect.aspx',
    'email': 'www.zhangbowudi@qq.com',
    'pwd': 'bobo328410948',
    'code': code_text,
    'denglu': '登陸',
}
print(code_text)
page_text = requests.post(login_url,headers=headers,data=data).text

with open('./login.html','w',encoding='utf-8') as fp:
    fp.write(page_text)

版本三 (完美版):

s = requests.Session()
#驗證碼的識別:將驗證碼下載到本地而後提交給打嗎平臺進行識別
main_url = 'https://so.gushiwen.org/user/login.aspx?from=http://so.gushiwen.org/user/collect.aspx'
page_text = s.get(main_url,headers=headers).text
tree = etree.HTML(page_text)
code_src = 'https://so.gushiwen.org'+tree.xpath('//*[@id="imgCode"]/@src')[0]

# request.urlretrieve(code_src,'./code.jpg')
code_data = s.get(code_src,headers=headers).content
with open('./code.jpg','wb') as fp:
    fp.write(code_data)

#解析出動態變化的請求參數
__VIEWSTATE = tree.xpath('//*[@id="__VIEWSTATE"]/@value')[0]
__VIEWSTATEGENERATOR = tree.xpath('//*[@id="__VIEWSTATEGENERATOR"]/@value')[0]

#識別驗證碼
code_text = transformCode('./code.jpg',1004)


login_url = 'https://so.gushiwen.org/user/login.aspx?from=http%3a%2f%2fso.gushiwen.org%2fuser%2fcollect.aspx'
data = {
    '__VIEWSTATE': __VIEWSTATE,
    '__VIEWSTATEGENERATOR': __VIEWSTATEGENERATOR,
    'from': 'http://so.gushiwen.org/user/collect.aspx',
    'email': 'www.zhangbowudi@qq.com',
    'pwd': 'bobo328410948',
    'code': code_text,
    'denglu': '登陸',
}
print(code_text)
page_text = s.post(login_url,headers=headers,data=data).text

with open('./login.html','w',encoding='utf-8') as fp:
    fp.write(page_text)
- 反爬機制
    - robots
    - UA檢測
    - 圖片懶加載 
    - 代理
    - cookie
    - 驗證碼
    - 動態變化的請求參數
    - 動態加載的數據

10. 使用線程池提高爬取數據的效率

# 同步操做

import time

start = time.time()
def request(url):
    print('正在請求',url)
    time.sleep(2)
    print('請求完畢:',url)
urls = [
    'www.1.com',
    'www.b.com',
    'www.3.com'
]

for url in urls:
    request(url)
print('總耗時:',time.time()-start)
# 異步操做

import time
from multiprocessing.dummy import Pool

start = time.time()
pool = Pool(3)
def request(url):
    print('正在請求',url)
    time.sleep(2)
    print('請求完畢:',url)

urls = [
    'www.1.com',
    'www.b.com',
    'www.3.com'
]

pool.map(request,urls)

print('總耗時:',time.time()-start)
# 爬蟲+ 線程池
# server端
from flask import Flask,render_template
from  time import sleep
app = Flask(__name__)

@app.route('/bobo')
def index_bobo():
    sleep(2)
    return render_template('ip.html')

@app.route('/jay')
def index_jay():
    sleep(2)
    return render_template('login.html')
app.run()

# 爬蟲 + 線程池
import time
from multiprocessing.dummy import Pool
import requests
from lxml import etree
start = time.time()
urls = [
    'http://localhost:5000/jay',
    'http://localhost:5000/bobo'
]

def get_request(url):
    page_text = requests.get(url).text
    return page_text

def parse(page_text):
    tree = etree.HTML(page_text)
    print(tree.xpath('//div[1]//text()'))

pool = Pool(2)
page_text_list = pool.map(get_request,urls)
pool.map(parse,page_text_list)
print(len(page_text_list))

print('總耗時:',time.time()-start)

11. 單線程+多任務的異步協程

1. 解釋說明

1. 特殊的函數:
    - 若是一個函數的定義被async修飾後,則該函數就是一個特殊的函數。
- 協程:
    - 對象。特殊函數被調用後,函數內部的實現語句不會被當即執行,而後該函數
        調用會返回一個協程對象。
    - 結論:協程對象==特殊的函數調用

2. 任務對象
    - 實際上是對協程對象的進一步封裝。
    - 結論:任務對象==高級的協程對象==特殊的函數調用
    - 綁定回調:
        - 回調函數何時被執行?
            - 任務對象執行結束後執行回調函數
        - task.add_done_callback(func)
            - func必需要有一個參數,該參數表示的是該回調函數對應的任務對象
            - 回調函數的參數.result():任務對象對應特殊函數內部的返回值
3. 事件循環對象
    - 做用:將其內部註冊的任務對象進行異步執行。

- 編碼流程:
    - 定義特殊函數
    - 建立協程對象
    - 封裝任務對象
    - 建立事件循環對象
    - 將任務對象註冊到事件循環中且開啓事件循環對象

- 注意:在特殊函數內部的實現語句中不能夠出現不支持異步的模塊對應的代碼,不然
    就是終止多任務異步協程的異步效果

- 注意重點:requests模塊不支持異步,在多任務的異步協程中不可使用requests

- aiohttp
    - 概念:支持異步的網絡請求模塊
    - 編碼流程:
        - 寫基本架構:
                with aiohttp.ClientSession() as s:
                    with s.get(url) as response:
                        page_text = response.text()
                        return  page_text
        - 補充細節:
            - 添加async關鍵字
                - 每個with前加上async
            - 添加await關鍵字
                - 加載每一步的阻塞操做前加上await
                    - 請求
                    - 獲取響應數據

2. 協程對象 - 特殊的函數

import asyncio
from time import sleep

#函數的定義 async修飾函數後成爲特殊的函數
async def get_request(url):
    print('正在請求:',url)
    sleep(1)
    print('請求結束:',url)

#函數調用:返回的就是一個協程對象
c = get_request('www.1.com')


#建立3個協程對象
urls = [
    '1.com','2.com','3.com'
]
coroutine_list = [] # 協程對象列表
for url in urls:
    c = get_request(url)
    coroutine_list.append(c)

print(coroutine_list)

# 注意,此時協程對象並不能被執行

3. 基於協程對象建立任務對象

import asyncio
from time import sleep

#函數的定義
async def get_request(url):
    print('正在請求:',url)
    sleep(1)
    print('請求結束:',url)


#函數調用:返回的就是一個協程對象
c = get_request('www.1.com')
#建立一個任務對象:基於協程對象建立
task = asyncio.ensure_future(c)


#建立3個協程對象
urls = [
    '1.com','2.com','3.com'
]
task_list = []  #存放多個任務對象的列表
for url in urls:
    c = get_request(url)
    task = asyncio.ensure_future(c) # 封裝任務對象
    task_list.append(task)

4. 事件循環對象

import asyncio
from time import sleep

#函數的定義
async def get_request(url):
    print('正在請求:',url)
    sleep(1)
    print('請求結束:',url)

c = get_request('www.1.com')

task = asyncio.ensure_future(c)

#建立一個事件循環對象
loop = asyncio.get_event_loop()
#將任務對象註冊到事件循環對象中而且開啓事件循環
loop.run_until_complete(task)

5. 多任務異步協程

import asyncio
from time import sleep
import time
#函數的定義
async def get_request(url):
    print('正在請求:',url)
    await asyncio.sleep(3) # time模塊不支持異步,用asyncio代替 await爲等待相關阻塞操做執行再執行
    print('請求結束:',url)


#建立3個協程對象
urls = [
    '1.com','2.com','3.com'
]

start = time.time()
#任務列表:存儲的是多個任務對象
tasks = []
for url in urls:
    c = get_request(url)
    task = asyncio.ensure_future(c)
    tasks.append(task)

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))  # wait爲掛起

print('總耗時:',time.time()-start)

6. 給任務對象綁定回調

import asyncio
from time import sleep
import time
#函數的定義
async def get_request(url):
    print('正在請求:',url)
    await asyncio.sleep(3)
    print('請求結束:',url)
    return 'bobo'

def parse(task): # 回調函數必需要有一個參數,task爲綁定的任務對象,task.result()返回的是特殊函數的返回值
    print('i am task callback()!!!=----',task.result())


#建立3個協程對象
urls = [
    '1.com','2.com','3.com'
]

start = time.time()
#任務列表:存儲的是多個任務對象
tasks = []
for url in urls:
    c = get_request(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)

7. 多任務異爬蟲

爬取本身搭建的服務器

服務端 :

from flask import Flask,render_template
from time import sleep
app = Flask(__name__)


@app.route('/ip')
def index_1():
    sleep(2)
    return render_template('ip.html')

@app.route('/ip1')
def index_2():
    sleep(2)
    return render_template('ip.html')

app.run(debug=True)

爬取

import asyncio
import requests
import time
import aiohttp
from lxml import etree
 
#特殊函數:發起請求獲取頁面源碼數據
# async def get_request(url):
#     #requests是一個不支持異步的模塊
#     page_text = requests.get(url).text
#     return page_text


async def get_request(url):
    async with aiohttp.ClientSession() as s:
        #get/post:proxy = 'http://ip:port'
        #url,headers,data/prames跟requests一直
        async with await s.get(url) as response:
            page_text = await response.text()#text()字符串形式的響應數據。read()二進制的響應數據
            return  page_text

def parse(task):
    page_text = task.result()
    tree = etree.HTML(page_text)
    print(tree.xpath('//*[@id="10"]//text()'))

urls = [
    'http://localhost:5000/ip1',
    'http://localhost:5000/ip',
    'http://localhost:5000/ip1',
    'http://localhost:5000/ip',
    'http://localhost:5000/ip1',
    'http://localhost:5000/ip',
]

start = time.time()

tasks = [] #任務列表
for url in urls:
    c = get_request(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)

12. selenium

selenium :
    - 概念:基於瀏覽器自動化的一個模塊。
    - Appium是基於手機的自動化的模塊。
    - selenium和爬蟲之間的關聯
        - 便捷的爬取到動態加載的數據
        - 可見便可得
        - 便捷的實現模擬登錄
    - 基本使用:
        - 環境安裝
            - pip install selenium
            - 下載瀏覽器的驅動程序
                - http://chromedriver.storage.googleapis.com/index.html
                - 瀏覽器版本和驅動程序的映射關係:          https://blog.csdn.net/huilan_same/article/details/51896672

    - 動做鏈
        - 在使用find系列的函數進行標籤訂位的時候若是出現了NoSuchElementException如何處理?
            - 若是定位的標籤是存在於iframe標籤之下的,則在進行指定標籤訂位的時候
                必須使用switch_to.frame()的操做纔可。

無頭瀏覽器 :
  - phantomjs
  - 谷歌無頭瀏覽器(推薦)

如何規避selenium被監測到的風險
    - 網站能夠根據:window.navigator.webdriver的返回值鑑定是否使用了selenium 
        (瀏覽器開發者工具的console窗口下測試)
          - undefind:正常
          - true:selenium 則檢測到了是selenium訪問的

selenium的基本使用

from selenium import webdriver
import time
#實例化某一款瀏覽器對象
bro = webdriver.Chrome(executable_path='chromedriver.exe')

#基於瀏覽器發起請求
bro.get('https://www.jd.com/')

#商品搜索
#標籤訂位
search_input = bro.find_element_by_id('key')
#往定位到的標籤中錄入數據
search_input.send_keys('襪子')
#點擊搜索按鈕
btn = bro.find_element_by_xpath('//*[@id="search"]/div/div[2]/button')
time.sleep(2)
btn.click()
time.sleep(2)
#滾輪滑動(js注入)
bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')

# time.sleep(6)
bro.quit() # 退出瀏覽器

捕獲動態加載的數據

from selenium import webdriver
import time
from lxml import etree
#實例化某一款瀏覽器對象
bro = webdriver.Chrome(executable_path='chromedriver.exe')

bro.get('https://www.fjggfw.gov.cn/Website/JYXXNew.aspx')
time.sleep(1)

#page_source:當前頁面全部的頁面源碼數據
page_text = bro.page_source

#存儲前3頁對應的頁面源碼數據
all_page_text = [page_text]

for i in range(3):
    next_page_btn = bro.find_element_by_xpath('//*[@id="kkpager"]/div[1]/span[1]/a[7]')
    next_page_btn.click()
    time.sleep(1)
    all_page_text.append(bro.page_source)

for page_text in all_page_text:
    tree = etree.HTML(page_text)
    title = tree.xpath('//*[@id="list"]/div[1]/div/h4/a/text()')[0]
    print(title)

動做鏈的使用

from selenium import webdriver
from selenium.webdriver import ActionChains #動做連的類
from time import sleep
bro = webdriver.Chrome(executable_path='chromedriver.exe')
bro.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')
sleep(1)

#  - 在使用find系列的函數進行標籤訂位的時候若是出現了NoSuchElementException如何處理?
#  - 若是定位的標籤是存在於iframe標籤之下的,則在進行指定標籤訂位的時候必須使用switch_to.frame()的操做纔可。
bro.switch_to.frame('iframeResult') #frame的參數爲iframe標籤的id屬性值
div_tag = bro.find_element_by_id('draggable')

#基於動做連實現滑動操做
action = ActionChains(bro)
#點擊且長按
action.click_and_hold(div_tag)

for i in range(5):
    #perform()表示讓動做連當即執行
    action.move_by_offset(20,0).perform()
    sleep(0.5)

sleep(3)
bro.quit()

無頭瀏覽器 - 無可視化界面

from selenium import webdriver
from time import sleep

from selenium.webdriver.chrome.options import Options
# 建立一個參數對象,用來控制chrome以無界面模式打開
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)
bro.get('https://www.taobao.com/')
bro.save_screenshot('./123.png')  # 截圖保存 圖片格式必須是png,不然報錯

print(bro.page_source)

規避網站對selenium的監測

from selenium import webdriver
from time import sleep

from selenium.webdriver import ChromeOptions

option = ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])


# 後面是你的瀏覽器驅動位置,記得前面加r'','r'是防止字符轉義的
bro = webdriver.Chrome(r'chromedriver.exe',options=option)

bro.get('https://www.taobao.com/')

13. 12306模擬登錄

# 超級鷹示例代碼
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
        self.base_params = {
            'user': self.username,
            'pass2': self.password,
            'softid': self.soft_id,
        }
        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()
from ChaoJiYing import Chaojiying_Client #導入超級鷹示例代碼

from selenium import webdriver
from selenium.webdriver import ActionChains
from time import sleep
#下載pil或者是Pillow
from PIL import Image


def transformCode(imgPath,imgType): # 超級鷹封裝的函數 返回的是一組座標
    chaojiying = Chaojiying_Client('bobo328410948', 'bobo328410948', '899370')
    im = open(imgPath, 'rb').read()
    return chaojiying.PostPic(im,imgType)['pic_str']


bro = webdriver.Chrome(executable_path='chromedriver.exe')

bro.get('https://kyfw.12306.cn/otn/login/init')
sleep(2)

bro.save_screenshot('main.png')

#在main.jpg中截取下驗證碼圖片
img_tag = bro.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img')

location = img_tag.location # 圖片左上角座標
size = img_tag.size #img標籤對應圖片的長寬(尺寸)
#裁剪範圍
rangle = (location['x'],location['y'],location['x']+size['width'],location['y']+size['height'])

# 裁剪12306驗證碼
i = Image.open('./main.png')
frame = i.crop(rangle)
frame.save('code.png')


result = transformCode('./code.png',9004)  
# 識別後的座標原點是基於識別圖片而來的
# 座標轉換 260,140|260,139  ==> [[260,140],[260,139]]
all_list = []#[[260,140],[260,139]]  # 座標列表
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)



for xy in all_list:
    x = xy[0]
    y = xy[1]
    # 要先找到識別圖片的標籤img_tag,而後基於識別圖片座標進行點擊事件
    ActionChains(bro).move_to_element_with_offset(img_tag,x,y).click().perform()
    sleep(1)

14. 空氣質量數據爬取

爬蟲分析

1. 在頁面中更換查找條件可讓抓包工具捕獲到咱們想要的數據包
2. apistudyapi.php該數據包就是咱們最終定位到的爬取數據對應的數據包
    a. 該數據包中能夠提取到url和請求參數(多是一組密文,而後該是動態變化)
    b. 響應數據的是通過加密的密文
3. 當修改了查詢條件後且點擊了查詢按鈕後發起了一個ajax請求,該請求就能夠請求到apistudyapi.php數據包
    a. 想要捕獲的數據是能夠經過點擊搜索按鈕生成的
4. 經過火狐瀏覽器的開發者工具能夠找到搜索按鈕綁定的點擊事件對應的事件函數(getData())
5. 分析getData():在該函數實現內部沒有找到ajax請求對應的操做
    a. type這個變量能夠爲HOUR
    b. getAQIData();getWeatherData();
6. 分析:getAQIData();getWeatherData();
   + 發現這兩個函數的實現除了method變量的賦值不一樣剩下的都一致
     a. method = (GETDETAIL 或者 GETCITYWEATHER)
     b. 在這兩個函數的實現中也沒有發現ajax請求對應的代碼,可是發現了一個叫作getServerData的函數調用,則分析a             jax請求對應的代碼確定是存在於getServerData這個函數的實現中
     c. getServerData(method, param,匿名函數,0.5)
        -method = (GETDETAIL 或者 GETCITYWEATHER)
        -param是一個字典,內部有四組(city,type,startTime,endTime)鍵值對
7.分析getServerData函數的實現 :
    a. 最終經過抓包工具的全局搜索定位到了該函數的實現,可是實現的js代碼被加密了,該種形式的加密被稱爲js混淆。
    b.如何破解js混淆?
         - http://www.bm8.com.cn/jsConfusion/進行js反混淆
    c. 在該函數的實現中終於找到了ajax請求對應的代碼:
             - ajax請求的url
             - ajax請求方式
             - 請求參數的來源:getParam(method, param)
             - 對加密的響應數據解密:decodeData(密文)
8.基於python模擬執行js代碼 :
     - PyExecJS模塊可讓python模擬執行js代碼
     - 環境安裝:
             - pip install PyExecJS
             - 在本機安裝nodejs的開發環境

1. 獲取ajax請求的動態變化且加密的請求參數(d:xxx)

此過程是本地化的

#獲取ajax請求的動態變化且加密的請求參數(d:xxx)
import execjs
node = execjs.get()  # 實例化一個對象
 
# Params
method = 'GETDETAIL'
city = '北京'
type = 'HOUR'
start_time = '2018-01-25 00:00:00'
end_time = '2018-01-25 23:00:00'
 
# Compile javascript
file = 'test.js'  # 將ajax請求的js代碼放在這裏
ctx = node.compile(open(file,encoding='utf-8').read())  # 編譯js代碼
 
# Get params
js = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time)
params = ctx.eval(js) #執行js代碼
print(params) # 獲取到的params是動態加密過的請求參數

2. 攜帶捕獲到請求參數進行請求 :

#發起post請求
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
}

url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'
response_text = requests.post(url, data={'d': params},headers=headers).text
print(response_text)  # 獲取到的響應數據是加密的

3. 對捕獲到的加密的響應數據進行解密

#對加密的響應數據進行解密
js = 'decodeData("{0}")'.format(response_text)
decrypted_data = ctx.eval(js)
print(decrypted_data)

4. 完整代碼

#對捕獲到的加密的響應數據進行解密
import execjs
import requests

node = execjs.get()
 
# Params
method = 'GETDETAIL'
city = '北京'
type = 'HOUR'
start_time = '2018-01-25 00:00:00'
end_time = '2018-01-25 23:00:00'
 
# Compile javascript
file = 'test.js'
ctx = node.compile(open(file,encoding='utf-8').read())
 
# Get params
js = 'getPostParamCode("{0}", "{1}", "{2}", "{3}", "{4}")'.format(method, city, type, start_time, end_time)
params = ctx.eval(js) #執行js代碼

#發起post請求
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
}

url = 'https://www.aqistudy.cn/apinew/aqistudyapi.php'
response_text = requests.post(url, data={'d': params},headers=headers).text

#對加密的響應數據進行解密
js = 'decodeData("{0}")'.format(response_text)
decrypted_data = ctx.eval(js)
print(decrypted_data)

15. scrapy爬蟲框架

1. scrapy框架

- scrapy框架
    - 高性能的網絡請求
    - 高性能的數據解析
    - 高性能的持久化存儲
    - 深度爬取
    - 全棧爬取
    - 分佈式
    - 中間件
    - 請求傳參

2. 環境的安裝

- 環境的安裝:
    - mac/linux:pip install scrapy
    - window:
        - pip install wheel - 安裝wheel工具
        - 下載twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted  - 下載twisted架構
        - 進入下載目錄,執行 pip install Twisted‑17.1.0‑cp35‑cp36m‑win_amd64.whl -安裝twisted 報錯換版本
            scrapy的異步是基於twisted實現的
        - pip install pywin32
        - pip install scrapy

3. 基本使用

- 基本使用 
    1. 新建一個工程 :pycharm終端中 :scrapy startproject ProName
        目錄結構:
            - spiders(包):空包
             - settings:配置文件
                    - 不聽從robots
                    - UA假裝
                    - 日誌等級的指定
    2. 命令行進入工程目錄中 : cd ProName3
    3. 在spiders(爬蟲文件夾(包))中建立一個py爬蟲文件 : scrapy genspider pachong www.xxx.com
        該pachong文件建立在了spiders文件夾中
    4. 編寫代碼 : 主要的爬蟲代碼寫在了爬蟲文件中
    5. 執行工程 : 命令行中 : scrapy crawl pachong
                scrapy crawl pachong --nolog 不顯示日誌

4. 爬蟲文件說明

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

# 爬蟲類 父類是 Spider
class PachongSpider(scrapy.Spider):
    name = 'pachong'    # name 爬蟲文件名 : 當前爬蟲源文件的惟一標識
    # 被容許的域名
    allowed_domains = ['www.xxx.com']
    # 起始的url,列表中存放的url均可以被scrapy進行異步網絡請求
    start_urls = ['http://www.xxx.com/']
    # parse用做數據解析,response爲響應對象
    def parse(self, response):
        pass

5. settings的一些配置

1. 指定打印日等級  LOG_LEVEL = 'ERROR'  
2. 不聽從robots協議  ROBOTSTXT_OBEY = False  
3. UA假裝 USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'

6. 數據解析+持久化存儲

- scrapy的數據解析
    - extract、extract_first()做用

通常流程 :

1. 新建工程

- 終端中 :
1. scrapy startproject scrapy01
2. cd scrapy01
2. scrapy genspider pachong www.xx.com

2. settings配置

USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
ROBOTSTXT_OBEY = False
LOG_LEVEL = 'ERROR'

3. 寫爬蟲代碼

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


class PachongSpider(scrapy.Spider):
    name = 'pachong'
    # allowed_domains = ['www.xx.com']
    start_urls = ['https://dig.chouti.com']

    def parse(self, response):
        # 解析數據
        div_list = response.xpath('/html/body/main/div/div/div[1]/div/div[2]/div[1]/div')
        for div in div_list:
            content = div.xpath('.//a[@class="link-title link-statistics"]/text()')[0].extract()
            content1 = div.xpath('.//a[@class="link-title link-statistics"]/text()').extract()[0]
            author = div.xpath('.//span[@class="left author-name"]/text()').extract_first()
            print(content,content1,author)
            # 測試 ,只取一個
            break

4. 執行爬蟲代碼

終端 : scrapy crawl pachong

持久化存儲

1、基於終端指令進行的持久化存儲

- 基於終端指令進行持久化存儲
        - 只能夠將parse方法的返回值存儲到本地的磁盤文件(指定形式後綴)中
        - scrapy crawl spiderName -o filePath
  1. 爬蟲代碼

    # -*- coding: utf-8 -*-
    import scrapy
    
    
    class PachongSpider(scrapy.Spider):
        name = 'pachong'
        # allowed_domains = ['www.xx.com']
        start_urls = ['https://dig.chouti.com']
    
        def parse(self, response):
            all_data = []
            # 解析數據
            div_list = response.xpath('/html/body/main/div/div/div[1]/div/div[2]/div[1]/div')
            for div in div_list:
                content = div.xpath('.//a[@class="link-title link-statistics"]/text()')[0].extract()
                content1 = div.xpath('.//a[@class="link-title link-statistics"]/text()').extract()[0]
                author = div.xpath('.//span[@class="left author-name"]/text()').extract_first()
                dic = {
                    'content':content,
                    'auther':author
                }
                all_data.append(dic)
            return  all_data
  2. 持久化存儲

    只支持後綴 'json', 'jsonlines', 'jl', 'csv', 'xml', 'marshal', 'pickle'
    終端命令 : scrapy crawl pachong -o data.csv

2、基於管道進行持久化存儲(重點)

- 基於管道進行持久化存儲(重點)
        - 編碼流程
            - 1.在爬蟲文件中進行數據解析
            - 2.在item類中定義相關的屬性
            - 3.將解析到的數據存儲到一個item類型的對象中
            - 4.將item類型的對象提交給管道
            - 5.管道類的process_item方法負責接受item,接受到後能夠對item實現任意形式的持久化存儲操做
            - 6.在配置文件中開啓管道

        - 一個管道類對應一種平臺的持久化存儲
  1. 爬蟲代碼 - 在爬蟲文件中進行數據解析

    # -*- coding: utf-8 -*-
    import scrapy
    # 導入Item的類,後期實例化須要
    from scrapy002.items import Scrapy002Item
    
    class PachongSpider(scrapy.Spider):
        name = 'pachong'
        # allowed_domains = ['www.xx.com']
        start_urls = ['https://dig.chouti.com']
    
        def parse(self, response):
            all_data = []
            # 解析數據
            div_list = response.xpath('/html/body/main/div/div/div[1]/div/div[2]/div[1]/div')
            for div in div_list:
                # xpath在取標籤的時候必需要用exetract()、exetract_first()進行字符串提取
                content = div.xpath('.//a[@class="link-title link-statistics"]/text()')[0].extract()
                author = div.xpath('.//span[@class="left author-name"]/text()').extract_first()
                # 實例化一個item類型的對象
                item = Scrapy002Item()
                # 給item對象的屬性賦值
                item['content'] = content
                item['author'] = author
                yield  item  # 將item提交給管道
  2. items.py - 在item類中定義相關的屬性

    import scrapy
    
    class Scrapy002Item(scrapy.Item):
        # Field 類型是一個萬能的數據類型
        author = scrapy.Field()
        content = scrapy.Field()
  3. pipelines.py - 將item類型的對象提交給管道 - 本地化存儲

    爬取的數據存儲到本地 :

    class Scrapy002Pipeline(object):
        # 重寫父類,且該方法只會執行一次
        def open_spider(self,spider):
            print('爬蟲開始 ...')
            self.fp = open('pachong_ingf.txt','w',encoding='utf-8')
        # 該方法調用後就可接收爬蟲類提交的item對象,且賦值給item參數
        def process_item(self, item, spider):
            author = item['author']
            content = item['content']
            self.fp.write(author+':'+content+'\n')
            return item
        def close_spider(self,spider):
            print('爬蟲結束!')
            self.fp.close()

    爬取的數據存儲到數據庫中 :

    1.在pipelines.py中寫存儲到數據庫的類

    class Scrapy002Pipeline(object):
        # 重寫父類,且該方法只會執行一次
        def open_spider(self,spider):
            print('爬蟲開始 ...')
            self.fp = open('pachong_ingf.txt','w',encoding='utf-8')
        # 該方法調用後就可接收爬蟲類提交的item對象,且賦值給item參數
        def process_item(self, item, spider):
            author = item['author']
            content = item['content']
            self.fp.write(author+':'+content+'\n')
            return item  # 此處是講item傳遞給下一個即將執行的管道類
        def close_spider(self,spider):
            print('爬蟲結束!')
            self.fp.close()
    
    
    import pymysql
    class Mysql_data(object):
        conn = None
        cursor = None
        def open_spider(self, spider):
            self.conn = pymysql.Connection(host='127.0.0.1',port = 3306,user = 'root',password='2108',db = 'pachong',charset='utf8')
    
        def process_item(self, item, spider):
            author = item['author']
            content = item['content']
    
            sql = 'insert into pachong values ("%s","%s")'%(author,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):
            self.cursor.close()
            self.conn.close()
    1. 在配置中註冊相應的管道

      ITEM_PIPELINES = {
         # 'scrapy002.pipelines.Scrapy002Pipeline': 300,
         'scrapy002.pipelines.Mysql_data': 301,
      }
  4. settings配置

    開啓管道,300爲優先級,數值越小.優先級越高
    ITEM_PIPELINES = {
       'scrapy002.pipelines.Scrapy002Pipeline': 300,
    }

16. scrapy的圖片數據爬取(數據流的爬取)

- scrapy的圖片數據爬取(流數據的爬取)
    - scrapy中封裝好了一個管道類(ImagesPipeline),基於該管道類能夠實現圖片資源的請求和持久化存儲
    - 編碼流程:
        - 爬蟲文件中解析出圖片的地址
        - 將圖片地址封裝到item中且提交給管道
        - 管道文件中自定義一個管道類(父類:ImagesPipeline)
        - 重寫三個方法:
            - def get_media_requests(self,item,info):
            - def file_path(self,request,response=None,info=None):
            - def item_completed(self,result,item,info):
        - 在配置文件中開啓管道且加上IMAGES_STORE = './imgLibs'
  1. 寫爬蟲代碼

    import scrapy
    from imgage_pa.items import ImgagePaItem
    
    class ImgSpider(scrapy.Spider):
        name = 'img'
        # allowed_domains = ['www.xx.com']
        start_urls = ['http://www.521609.com/xiaoyuanmeinv/']
    
        def parse(self, response):
            li_list = response.xpath('//*[@id="content"]/div[2]/div[2]/ul/li')
            for li in li_list:
                item = ImgagePaItem()
                img_src = 'http://www.521609.com' + li.xpath('./a[1]/img/@src').extract_first()
                item['img_src'] = img_src
                yield item
  2. items.py - 在item類中定義相關的屬性

    import scrapy
    
    class ImgagePaItem(scrapy.Item):
        img_src = scrapy.Field()
  3. pipelines.py - 將item類型的對象提交給管道

    from scrapy.pipelines.images import ImagesPipeline
    import scrapy
    class ImgagePaPipeline(ImagesPipeline):
        # 該方法是用於請求發送的
        def get_media_requests(self, item, info):
            print(item)
            yield scrapy.Request(url=item['img_src'])
    
        # 指定圖片路徑 (文件夾 + 文件名)
        def file_path(self, request, response=None, info=None):
            return  request.url.split('/')[-1]
    
        # 將item傳遞給下一個即將執行的管道類
        def item_completed(self, results, item, info):
            return item
  4. settings配置

    USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
    
    ROBOTSTXT_OBEY = False
    LOG_LEVEL = 'ERROR'
    IMAGES_STORE = './images'  #指定爬取的圖片存放的路徑
    ITEM_PIPELINES = {
       'imgage_pa.pipelines.ImgagePaPipeline': 300,
    }

17. 在scrapy中如何進行手動請求發送

 

實現多頁數據爬取

# 爬蟲代碼py文件中
# -*- coding: utf-8 -*-
import scrapy
from imgage_pa.items import ImgagePaItem


class ImgSpider(scrapy.Spider):
    name = 'img'
    # allowed_domains = ['www.xx.com']
    start_urls = ['http://www.521609.com/xiaoyuanmeinv/']

    # 定義個通用的url模板
    url = 'http://www.521609.com/xiaoyuanmeinv/list_%d.html'
    page_Num = 1

    def parse(self, response):
        li_list = response.xpath('//*[@id="content"]/div[2]/div[2]/ul/li')
        for li in li_list:
            item = ImgagePaItem()
            img_src = 'http://www.521609.com' + li.xpath('./a[1]/img/@src').extract_first()
            item['img_src'] = img_src
            yield item

        if self.page_Num < 5: # 遞歸結束條件 - 爬取頁碼數
            self.page_Num += 1
            new_url = format((self.url % self.page_Num))
            yield scrapy.Request(new_url, callback=self.parse)  # 遞歸調用parse

在scrapy中如何進行手動請求發送

在scrapy中如何進行post請求的發送?

如何對起始的url進行post請求的發送?

- 在scrapy中如何進行手動請求發送
    - yield scrapy.Request(url,callback)
- 在scrapy中如何進行post請求的發送?
    - yield scrapy.FormRequest(url,callback,formdata)
- 如何對起始的url進行post請求的發送?
       - 重寫父類的start_requests(self):
            def start_requests(self):
                for url in self.start_urls:
                    yield scrapy.FormRequest(url,callback=self.parse,formdata={})  # 調用parse函數

18. 在scrapy中如何提高爬取數據的效率?

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

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

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

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

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

19. 請求傳參 + 核心組件

- 請求傳參(深度爬取)
    - 深度爬取:
        - 爬取的數據沒有存在同一張頁面中。
    - 如何實現請求傳參
        - Request(url,callback,meta={}):能夠將meta字典傳遞給callback
        - callback接收item:response.meta

請求傳參

爬蟲代碼

# -*- coding: utf-8 -*-
import scrapy
from movie.items import MovieItem

class MoviePaSpider(scrapy.Spider):
    name = 'movie_pa'
    # allowed_domains = ['www.xx.com']
    start_urls = ['https://www.4567tv.tv/index.php/vod/show/class/%E7%A7%91%E5%B9%BB/id/8.html  ']
    # 通用URL模板
    url = 'https://www.4567tv.tv/index.php/vod/show/class/科幻/id/8/page/%d.html'
    pageNum = 2
    # 解析電影名稱和詳情頁的url
    def parse(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/div/h4/a/text()').extract_first()
            detail_url ='https://www.4567tv.tv'+ li.xpath('./div/div/h4/a/@href').extract_first()
            # 實例化item
            item = MovieItem()
            item['title'] = title
            # 手動請求發送
            yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item})
        # 爬取多頁
        if self.pageNum < 5:
            new_url = format(self.url%self.pageNum)
            self.pageNum += 1
            yield  scrapy.Request(new_url,callback = self.parse)
    # 解析詳情頁中的電影簡介
    def parse_detail(self,response):
        item = response.meta['item']
        detail_content = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first()
        item['detail_content'] = detail_content
        yield item

items

import scrapy

class MovieItem(scrapy.Item):

    title = scrapy.Field()
    detail_content = scrapy.Field()

管道pipelines.py

class MoviePipeline(object):
    def process_item(self, item, spider):
        print(item)
        return item

settings配置

省略

scrapy的五大核心組件

1. 引擎(Scrapy)
    用來處理整個系統的數據流處理, 觸發事務(框架核心)
2. 調度器(Scheduler)
    用來接受引擎發過來的請求, 壓入隊列中, 並在引擎再次請求的時候返回. 能夠想像成一個URL(抓取網頁的網址或者說是連接)的優先隊列, 由它來決定下一個要抓取的網址是什麼, 同時去除重複的網址,放在隊列中
3. 下載器(Downloader)
    用於下載網頁內容, 並將網頁內容返回給蜘蛛(Scrapy下載器是創建在twisted這個高效的異步模型上的)
4. 爬蟲(Spiders)
    爬蟲是主要幹活的, 用於從特定的網頁中提取本身須要的信息, 即所謂的實體(Item)。用戶也能夠從中提取出連接,讓Scrapy繼續抓取下一個頁面
5. 項目管道(Pipeline)
    負責處理爬蟲從網頁中抽取的實體,主要的功能是持久化實體、驗證明體的有效性、清除不須要的信息。當頁面被爬蟲解析後,將被髮送到項目管道,並通過幾個特定的次序處理數據。

20. scrapy的中間件

1. 有哪些中間件。
        - 下載中間件(推薦)
        - 爬蟲中間件
2. 下載中間件的做用
        - 批量攔截全部的請求和響應
3. 爲何攔截請求
        - 篡改請求的頭信息(UA)
            - request.headers['User-Agent'] = 'xxxxx'
        - 代理
            - request.meta['proxy'] = 'http://ip:port'

4. 爲何攔截響應
        - 篡改響應數據
        - 篡改響應對象(推薦)

中間件的使用

  1. 寫爬蟲代碼

    import scrapy
    
    
    class MiddleSpider(scrapy.Spider):
        name = 'middle___'
        # allowed_domains = ['www.xx.com']
        start_urls = ['http://www.baidu.com/']
    
        def parse(self, response):
            pass
  2. 寫中間件

    from scrapy import signals
    import random
    # 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",
            "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 = [
        'http://153.180.102.104:80',
        'http://195.208.131.189:56055',
    ]
    PROXY_https = [
        'https://120.83.49.90:9000',
        'https://95.189.112.214:35508',
    ]
    class NewsWangyiDownloaderMiddleware(object):
    
    
        # 攔截請求與
        def process_request(self, request, spider):
            # 能夠將攔截到的請求儘量多的設置隨機的UA
            request.headers['User-Agent'] = random.choice(user_agent_list)
            # 代理ip的設置
            # if request.url.split(':')[0] == 'http':
            #     request.meta['proxy'] = random.choice(PROXY_http)
            # else:
            #     request.meta['proxy'] = random.choice(PROXY_https)
            return None
        # 攔截全部的響應
        def process_response(self, request, response, spider):
            print('攔截響應')
            return response
        # 攔截異常的請求
        def process_exception(self, request, exception, spider):
    
            print('異常攔截')
            # 將修正後的請求對象進行發送
            return request # 將異常的請求從新發送 可是不會循環發送,發送幾個後仍是異常就再也不進行發送
  3. settings配置中開啓中間件

    DOWNLOADER_MIDDLEWARES = {
       'news_wangyi.middlewares.NewsWangyiDownloaderMiddleware': 543,
    }

網易新聞的爬取

網易新聞(國內,國際,軍事,航空,無人機)新聞數據的標題和內容 :

分析以下:

1.每一個板塊下對應的新聞數據都是動態加載的
2.會對五個板塊的響應數據進行數據解析,可是板塊對應的響應對象是不包含動態加載的新聞數據,目前
  獲取的每個板塊對應的響應對象是不知足需求的響應對象!!!
3.將不知足需求的5個響應對象(工程中一共會有1+5+n),修改爲知足需求。
        - 找到指定的5個不知足需求的響應對象(中間件)
- 你的redis若是不能夠寫入字典
    - pip install -U redis==2.10.6

爬蟲過程

1. 新建工程

建工程 : scrapy startproject wangyiNews

建爬蟲文件 :

cd wangyiNews

scrapy genspider wangyipachong

2. 寫爬蟲代碼

import scrapy
from selenium import webdriver
from wangyiNews.items import WangyinewsItem

class WangyiPaSpider(scrapy.Spider):
    name = 'wangyi_pa'
    # allowed_domains = ['www.xx.com']
    start_urls = ['https://news.163.com/']
    module_urls = []  # 五個板塊所對應的url

    bro = webdriver.Chrome(executable_path=r'E:\Coding_Peasant\Project\爬蟲\chromedriver.exe')
    # 解析出每個板塊所對應的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')
        # 獲取 國內,國際,軍事,航空,無人機板塊的url
        indexs = [3, 4, 6, 7, 8]
        for index in indexs:
            li_tag = li_list[index]
            # 解析到每個板塊對應的url
            module_url = li_tag.xpath('./a/@href').extract_first()
            # 將解析到的每個板塊的url放到module_url屬性中,傳遞給中間件
            self.module_urls.append(module_url)
            # 對板塊的url進行請求發送獲取到每個板塊對應的頁面
            # 手動發送請求
            yield scrapy.Request(module_url, callback=self.parse_each_page)

    # 自定義解析每一個板塊下對應的每個頁面的新聞標題
    def parse_each_page(self, response):
        div_list = response.xpath('/html/body/div/div[3]/div[4]/div[1]/div/div/ul/li/div/div')
        for div in div_list:
            item = WangyinewsItem()
            detail_url = div.xpath('./a/@href').extract_first()
            title = div.xpath('./div/div[1]/h3/a/text()').extract_first()
            if title and detail_url:
                item['title']=title
                # 交給parse_detail解析新聞詳情
                yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item})
    # 解析新聞內容
    def parse_detail(self,response):
        item = response.meta['item']
        # //text() 返回的是列表,因此要用extract()
        content = response.xpath('//*[@id="endText"]//text()').extract()
        # 將新聞內容content列表轉換成
        content = ''.join(content)
        item['content'] = content
        yield item
    # 該方法在最後執行,關閉瀏覽器
    def closed(self,spider):
        self.bro.quit()

3. 中間件攔截響應

from scrapy import signals
from scrapy.http import HtmlResponse
from time import sleep
class WangyinewsDownloaderMiddleware(object):

    def process_request(self, request, spider):
        return None
    # 能夠攔截到1+5+n個響應對象
    def process_response(self, request, response, spider):
        # 根據5個板塊的url定位到指定的request
        # 根據request定位到指定的response
        # 實例化selenium
        bro = spider.bro
        # spider是爬蟲類實例化好的對象
        module_urls = spider.module_urls
        if request.url in module_urls:
            # response就是最終五大板塊對應的響應對象
            bro.get(request.url)
            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
            # url是響應請求所對應的url, body是獲取到的響應數據
            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

4. items

import scrapy

class WangyinewsItem(scrapy.Item):
    title = scrapy.Field()
    content = scrapy.Field()

5.管道pipelines

import pymysql
from redis import Redis
# 將爬取到的數據存儲在sql中
class WangyinewsPipeline(object):
    conn = None
    cursor = None
    def open_spider(self,spider):
        self.conn = pymysql.Connection(host='127.0.0.1',port=3306,user='root',password='2108',db='pachongend',charset='utf8')
        print(self.conn)
    def process_item(self, item, spider):
        sql = 'insert into news values ("%s","%s")'%(item['title'],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):
        self.cursor.close()
        self.conn.close()


# 將爬取到的數據存儲在redis中
class Wangyi_redis(object):
    conn = None
    def open_spider(self,spider):
        self.conn = Redis(host='127.0.0.1',port=6379)
        print(self.conn)
    def process_item(self,item,spider):
        self.conn.lpush('news',item['title'])
        self.conn.lpush('content',item['content'])

6. settings配置

USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
ROBOTSTXT_OBEY = False
LOG_LEVEL = 'ERROR'
# 開啓管道
ITEM_PIPELINES = {
   # 'wangyiNews.pipelines.WangyinewsPipeline': 300,
   'wangyiNews.pipelines.Wangyi_redis': 300,
}

基於百度AI的天然語言處理

http://ai.baidu.com/

http://ai.baidu.com/
天然語言處理 → 建立應用 → 進入文檔 → 天然語言處理 → 語言處理基礎技術 → SDK文檔 → Python語言

1. 安裝天然語言處理Python SDK

幫助文檔

pip install baidu-aip

示例代碼 :

""" 你的 APPID AK SK """
APP_ID = '17533126'
API_KEY = '7W0ed4MYLHzlfke8ZoM1FwRr'
SECRET_KEY = '0Gq28MBrwV4Yipyt07cxC9ON0xRichRb'

client = AipNlp(APP_ID, API_KEY, SECRET_KEY)


title = "歐洲冠軍盃足球賽"

content = "歐洲冠軍聯賽是歐洲足球協會聯盟主辦的年度足球比賽,表明歐洲俱樂部足球最高榮譽和水平,被認爲是全世界最高素質、最具影響力以及最高水平的俱樂部賽事,亦是世界上獎金最高的足球賽事和體育賽事之一。"


""" 調用文章標籤 """
dic = client.topic(title, content)
print(dic)

文章標籤和文章分類處理 :

將百度的aip做用到管道上便可

import pymysql
from redis import Redis
from aip import AipNlp

# 將爬取到的數據存儲在sql中
class WangyinewsPipeline(object):
    conn = None
    cursor = None
    def open_spider(self,spider):
        self.conn = pymysql.Connection(host='127.0.0.1',port=3306,user='root',password='2108',db='pachongend',charset='utf8')
        print(self.conn)
    def process_item(self, item, spider):
        sql = 'insert into news values ("%s","%s")'%(item['title'],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):
        self.cursor.close()
        self.conn.close()


# 將爬取到的數據存儲在redis中
class Wangyi_redis(object):
    conn = None
    client = None
    def open_spider(self,spider):
        """ 你的 APPID AK SK """
        APP_ID = '17533126'
        API_KEY = '7W0ed4MYLHzlfke8ZoM1FwRr'
        SECRET_KEY = '0Gq28MBrwV4Yipyt07cxC9ON0xRichRb'

        self.client = AipNlp(APP_ID, API_KEY, SECRET_KEY)

    def process_item(self,item,spider):
        title = item['title']
        content = item['content']
        content = content.replace(u'\xa0',u'')  # 解決gbk對'\xa0'編碼的問題
        
        n_tag = self.client.keyword(title, content)['items'][0]['tag']
        n_type = self.client.topic(title, content)['item']['lv2_tag_list'][0]['tag']
        print(n_tag,n_type)

21. CrawlSpider實現的全站數據的爬取

CrawlSpider實現的全棧數據的爬取
1. 新建工程 srcapy startproject ProName
2. cd 工程
3. 建立爬蟲文件:scrapy genspider -t crawl spiderName www.xxx.com
    配置settings
4. 鏈接提取器LinkExtractor
        能夠根據指定的規則對指定的鏈接進行提取【提取的規則就是構造方法中的allow(‘正則表達式’)參數決定】
5. 規則解析器Rule
        能夠將將鏈接提取器提取到的鏈接進行請求發送,能夠根據指定的規則(callback)對請求到的數據進行解析
6. follow=True
        將鏈接提取器 繼續做用到 鏈接提取器提取到的鏈接 所對應的 頁面源碼中
7. 執行爬蟲命令 : scrapy ceawl py文件名
請求發送的三種方式:
    1. 起始url
    2. scrapy.Request()
    3. 連接提取器發送請求提取器

通常爬蟲:

  1. settings配置

  2. 寫爬蟲代碼

    # -*- coding: utf-8 -*-
    import scrapy
    from scrapy.linkextractors import LinkExtractor
    from scrapy.spiders import CrawlSpider, Rule
    
    
    class SunSpider(CrawlSpider):
        name = 'sun'
        # allowed_domains = ['www.xx.com']
        # 起始url不作解析
        start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page=']
        # link 連接提取器:根據指定的規則對指定的連接進行提取連接
        # 提取的規則就是構造方法中的allow('正則表達式')參數決定
        # LinkExtractor能夠對頁面的全部url進行提取allow=r''表示的頁面的全部url,
        link = LinkExtractor(allow=r'type=4&page=\d+')
        rules = (
            # 實例化一個rule對象(規則解析對象)
            # 對提取到的連接自動發送請求,根據指定的規則callback='parse_item'對請求的對象解析
            # follow=True 表示的是將提取到的連接繼續做用到 連接提取器上再次進行新提取的連接頁面的連接提取
            Rule(link, callback='parse_item', follow=False ),
        )
    
        # 數據解析:解析提取連接請求所返回的響應
        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()
                status = tr.xpath('./td[3]/span/text()').extract_first()
                print(title,status)

基於CrawlSpider的深度爬取

1. settings配置

USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
ROBOTSTXT_OBEY = False
LOG_LEVEL = 'ERROR'
# 開啓管道
ITEM_PIPELINES = {
   'sun_pa.pipelines.SunPaPipeline': 300,
}

2. 爬蟲代碼

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from sun_pa.items import SunPaItem1,SunPaItem2

class SunSpider(CrawlSpider):
    name = 'sun'
    # allowed_domains = ['www.xx.com']
    # 起始url不作解析
    start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page=']
    # link 連接提取器:根據指定的規則對指定的連接進行提取連接
    # 提取的規則就是構造方法中的allow('正則表達式')參數決定
    # LinkExtractor能夠對頁面的全部url進行提取allow=r''表示的頁面的全部url,
    link = LinkExtractor(allow=r'type=4&page=\d+')
    link1 = LinkExtractor(allow=r'type=4&page=$') # 第一頁
    link_detail = LinkExtractor(allow=r'/question/\d+/\d+\.shtml') # \.轉義點
    rules = (
        # 實例化一個rule對象(規則解析對象)
        # 對提取到的連接自動發送請求,根據指定的規則callback='parse_item'對請求的對象解析
        # follow=True 表示的是將提取到的連接繼續做用到 連接提取器上再次進行新提取的連接頁面的連接提取
        Rule(link, callback='parse_item', follow=False ),
        Rule(link1, callback='parse_item', follow=False ),
        Rule(link_detail, callback='parse_detail', follow=False ),
    )

    # 數據解析:解析提取連接請求所返回的響應
    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()
            status = tr.xpath('./td[3]/span/text()').extract_first()
            num = tr.xpath('./td[1]/text()').extract_first()
            item = SunPaItem2()
            item['title']=title
            item['status']=status
            item['num']=num
            yield item
    # 解析詳情頁中的新聞內容
    def parse_detail(self, response):
        content = response.xpath('/html/body/div[9]/table[2]//tr[1]/td//text()').extract()
        content = ''.join(content)
        num = response.xpath('/html/body/div[9]/table[1]//tr/td[2]/span[2]/text()').extract_first()
        num = num.split(':')[-1]
        item = SunPaItem1()
        item['content']= content
        item['num']= num
        yield item

3. items

import scrapy


class SunPaItem1(scrapy.Item):
    content = scrapy.Field()
    num = scrapy.Field()

class SunPaItem2(scrapy.Item):
    title = scrapy.Field()
    status = scrapy.Field()
    num = scrapy.Field()

4. 持久化存儲

class SunPaPipeline(object):
    def process_item(self, item, spider):
        if item.__class__.__name__ == 'SunPaItem1':
            content = item['content']
            num = item['num']
            print(num,content)
        else:
            title = item['title']
            status = item['status']
            num = item['num']
            print(num,status,title)
        # 最後根據相應的num進行持續化存儲
        return item

這個網站的前5頁和最後頁的標籤格式不一樣,因此上述的基於CrawlSpider的深度爬取等我基於正則匹配url不能解析到最後頁的url,爬取不到數據,下面更改用手動請求進行爬取

下面只附上爬蟲代碼

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from sun_pa.items import SunPaItem2

class SunSpider(CrawlSpider):
    name = 'sun'
    # allowed_domains = ['www.xx.com']
    # 起始url不作解析
    start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page=']
    # link 連接提取器:根據指定的規則對指定的連接進行提取連接
    # 提取的規則就是構造方法中的allow('正則表達式')參數決定
    # LinkExtractor能夠對頁面的全部url進行提取allow=r''表示的頁面的全部url,
    link = LinkExtractor(allow=r'type=4&page=\d+')
    link1 = LinkExtractor(allow=r'type=4&page=$') # 第一頁
    rules = (
        # 實例化一個rule對象(規則解析對象)
        # 對提取到的連接自動發送請求,根據指定的規則callback='parse_item'對請求的對象解析
        # follow=True 表示的是將提取到的連接繼續做用到 連接提取器上再次進行新提取的連接頁面的連接提取
        Rule(link, callback='parse_item', follow=False ),
        Rule(link1, callback='parse_item', follow=False ),

    )

    # 數據解析:解析提取連接請求所返回的響應
    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()
            status = tr.xpath('./td[3]/span/text()').extract_first()
            detail_url = tr.xpath('./td[2]/a[2]/@href').extract_first()
            item = SunPaItem2()
            item['title']=title
            item['status']=status
            yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item})
    # 解析詳情頁中的新聞內容
    def parse_detail(self, response):
        item = response.meta['item']
        content = response.xpath('/html/body/div[9]/table[2]//tr[1]//text()').extract()
        content = ''.join(content)
        item['content'] = content
        yield item

22. 基於分佈式的爬蟲

1. 實現方式:scrapy+scrapy_redis組件實現的分佈式。scrapy+redis
2. 原生的scrapy是不能夠實現分佈式的!!!
3. 什麼是分佈式
        - 須要搭建一個由n臺電腦組成的機羣,而後在每一臺電腦中執行同一組程序,讓其對同一個網絡資源
            進行聯合且分佈的數據爬取。

4. 爲何scrapy不能夠實現分佈式
        - 調度器不能夠被共享
        - 管道不能夠被共享
5. scrapy-reids組件的做用是什麼
        - 提供能夠被共享的管道和調度器
分佈式的實現流程
    1. 環境的安裝 : pip install scrapy-redis
    2. 建立工程 : scrapy startproject ProName
    3. cd 工程
    4. 爬蟲文件的建立的兩種方式
        1. 基於Spider : scrapy genspider spiderName
        2. 基於CrawSpider : scrapy genspider -t crawl spiderName www.xxx.com
    5. 修改爬蟲文件
        1. 導包 :
            1. 基於CrawlSpider爬蟲 : from scrapy_redis.spiders import RedisCrawlSpider
            2. 基於Spider爬蟲文件 : from scrapy_redis.spiders import RedisSpider
        2. 將當前爬蟲類的父類修改成 RedisCrawlSpider
        3. 刪除allowed_domains 和 start_urls
        4. 添加一個redis_key = 'xx'的屬性,表示的是調度器隊列的名稱
        5. 根據常規形式編寫爬蟲代碼
    6. 修改settings配置文件
            1. 指定管道
            ITEM_PIPELINES = {
                        'scrapy_redis.pipelines.RedisPipeline': 400
                    }
            2. 指定調度器
                1. 增長了一個去重容器類的配置, 做用使用Redis的set集合來存儲請求的指紋數據, 從而實現請求去重                   的持久化
                DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
                2. 使用scrapy-redis組件本身的調度器
                SCHEDULER = "scrapy_redis.scheduler.Scheduler"
                3. 配置調度器是否要持久化, 也就是當爬蟲結束了, 要不要清空Redis中請求隊列和去重指紋的set。如                   果是True, 就表示要持久化存儲, 就不清空數據, 不然清空數據
                SCHEDULER_PERSIST = True
        3. 指定redis
            REDIS_HOST = '192.168.13.254' #須要存儲redis數據的電腦的ip
            REDIS_PORT = 6379
    7. 修改redis的配置文件(redis.windows.conf)
        1. 關閉默認綁定
            註釋掉 56行中的 : bind 127.0.0.1 # 註釋掉後任何人均可以訪問redis數據庫
        2. 關閉保護模式   
            75行 : protected-mode no # 不關閉的話,其餘電腦訪問只能讀數據,不能寫數據
    8. 啓動redis的服務端(攜帶配置文件)和客戶端 
        啓動服務端的代碼 :redis-server.exe redis.windows.conf
    9. 啓動分佈式的爬蟲程序
        1. cd 爬蟲文件所對應的目錄中
        2. 執行命令 :   scrapy runspider 爬蟲文件.py 執行後服務就開始監聽
        3. 向調度器的隊列放一個起始url (隊列是存在於客戶端的redis中的) : redis客戶端 : lpush sun                 www.xx.com  (www.xx.com是起始url)
        4. redis中就能夠查看爬取 的數據了
        5. redis的requests是已經爬取過的url列表
    10. 實現機羣分佈式爬取
        
        1. 修改redis的配置文件(redis.windows.conf)
        2. 啓動redis的服務端(攜帶配置文件)和客戶端 
        3. cd 爬蟲文件所對應的目錄中
                執行命令 :  
                scrapy runspider 爬蟲文件.py
                等待放入起始url

爬蟲代碼:

import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from fbsPro.items import FbsproItem
from scrapy_redis.spiders import RedisCrawlSpider
from scrapy_redis.spiders import RedisSpider
class FbsSpider(RedisCrawlSpider):
    name = 'fbs'
    # allowed_domains = ['www.xxx.com']
    # start_urls = ['http://www.xxx.com/']
    redis_key = 'sun' #可被共享的調度器隊列的名稱
    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()
            status = tr.xpath('./td[3]/span/text()').extract_first()
            item = FbsproItem()
            item['title'] = title
            item['status'] = status

            yield item

items

import scrapy


class FbsproItem(scrapy.Item):
    # define the fields for your item here like:
    title = scrapy.Field()
    status = scrapy.Field()

settings配置

USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
ROBOTSTXT_OBEY = False
LOG_LEVEL = 'ERROR'

#指定管道
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 = '192.168.13.254'
REDIS_PORT = 6379

23. 基於Celery的分佈式/Pypperteer(異步的scrapy)

基於Celery的分佈式/Pypperteer(異步的scrapy)

python芹菜 - Celery

24. 增量式

增量式
    - 概念:監測
    - 核心技術:去重
    - 適合使用增量式的網站:
        - 基於深度爬取
            - 對爬取過的頁面的url進行一個記錄(記錄表)
        - 基於非深度爬取
            - 記錄表:爬取過的數據對應的數據指紋
                - 數據指紋:就是原始數據的一組惟一標識

    - 所謂的記錄表是以怎樣的形式存在於哪?
        - redis的set充當記錄表

基於CrawlSpider的增量式爬蟲示例 - 基於深度爬取

1. 新建工程

scrapy startproject pachongcd pachongscrapy genspider -t crawl pachong_py www.xx.com

2. 配置settings

  1. 配置UA
  2. 配置Rebots
  3. 配置日誌等級
  4. 開啓相應管道

3. 寫爬蟲代碼

# -*- coding: utf-8 -*-
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from redis import Redis  # 導入redis模塊
from pachong.items import PachongItem

class PachongPySpider(CrawlSpider):
    name = 'pachong_py'
    # allowed_domains = ['www.xx.com']
    start_urls = ['https://www.4567tv.tv/index.php/vod/show/class/%E5%8A%A8%E4%BD%9C/id/1.html']
    conn = Redis(host='127.0.0.1',port=6379) # 實例化redis管道
    rules = (
        Rule(LinkExtractor(allow=r'page/\d+\.html'), callback='parse_item', follow=True),
    )

    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:
            movie_name = li.xpath('./div/div/h4/a/text()').extract_first()
            detail_url = 'https://www.4567tv.tv'+ li.xpath('./div/div/h4/a/@href').extract_first()
            # 向redis插入數據,movie_url爲自定義的數據字段
            ex = self.conn.sadd('movie_url',detail_url)
            # redis插入數據有返回值,若返回值是1,說明redis的集合set中沒有此條新數據,若爲0,說明有此條數據,不能插入
            if ex == 1: # 返回值爲1 ,則說明沒有爬取過,而後執行回調爬取
                print('有更新,正在爬取...')
                item = PachongItem()
                item['title'] = movie_name
                yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item})
            else: # 爬取過,不須要再次爬取
                print('沒有更新的電影數據!!')

    def parse_detail(self,response):
        item = response.meta['item']
        content = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first()
        item['content'] = content
        yield item

4. 寫items

import scrapy

class PachongItem(scrapy.Item):

    title = scrapy.Field()
    content = scrapy.Field()

5. 進入管道進行持久化存儲 (存入redis) - 注意開管道

class PachongPipeline(object):
    def process_item(self, item, spider):
        conn = spider.conn
        conn.lpush('movie_data',item)
        return item

25. 反爬機制

- 反爬機制
    - robots
    - UA假裝
    - 圖片懶加載
    - 驗證碼
    - cookie
    - 動態加載的數據
    - 動態變化的請求參數
    - js加密
    - js混淆
    - 代理
相關文章
相關標籤/搜索