網路爬蟲(python專案)

蘭亭落雪發表於2018-12-04

一.request+正規表示式爬取貓眼電影

1.什麼是request

Requests是用python語言基於urllib編寫的,採用的是Apache2 Licensed開源協議的HTTP庫
如果你看過上篇文章關於urllib庫的使用,你會發現,其實urllib還是非常不方便的,而Requests它會比urllib更加方便,可以節約我們大量的工作。(用了requests之後,你基本都不願意用urllib了)一句話,requests是python實現的最簡單易用的HTTP庫,建議爬蟲使用requests庫。

預設安裝好python之後,是沒有安裝requests模組的,需要單獨通過pip安裝

import requests

response  = requests.get("https://www.baidu.com")
print(type(response))
print(response.status_code)
print(type(response.text))
print(response.text)
print(response.cookies)
print(response.content)
print(response.content.decode("utf-8"))

##我們可以看出response使用起來確實非常方便,這裡有個問題需要注意一下:
很多情況下的網站如果直接response.text會出現亂碼的問題,所以這個使用response.content
這樣返回的資料格式其實是二進位制格式,然後通過decode()轉換為utf-8,這樣就解決了通過response.text直接返回顯示亂碼的問題.

請求發出後,Requests 會基於 HTTP 頭部對響應的編碼作出有根據的推測。當你訪問 response.text 之時,Requests 會使用其推測的文字編碼。你可以找出 Requests 使用了什麼編碼,並且能夠使用 response.encoding 屬性來改變它.如:

response =requests.get("http://www.baidu.com")
response.encoding="utf-8"
print(response.text)

不管是通過response.content.decode("utf-8)的方式還是通過response.encoding="utf-8"的方式都可以避免亂碼的問題發生

 

二..利用Ajax分析使用代理處理反爬抓取今日頭條街拍美圖.

有些網頁直接請求得的HTML程式碼並沒有我們在瀏覽器看到的內容,因為一些資訊是通過Ajax載入並且經過js渲染生成的。

網路庫:request

解析庫:BeautifulSoup 正規表示式

儲存庫:MongoDB (用到pymongo庫)

 

1、分析網站原始碼。

網站是區域性動態變化,offset變化載入內容,變化範圍為0,20,40···

首先要獲取索引頁程式碼資料,定義索引頁,由於是ajax請求,offset根據索引變化0、20、40···,用requests獲取網頁程式碼,urlcode將字典轉換為url請求引數,然後異常處理,根據url_code狀態碼判斷請求是否成功,返回文字格式,最後定義main函式呼叫。可根據可變引數’offset’和’keyworld’引數呼叫函式,改為def get_page-index(offset,keyworld)

from urllib.parse import urlencode
import requests
from requests.exceptions import RequestException

def get_page_index():
    data = {
        'offset': 0,
        'format': 'json',
        'keyword': '街拍',
        'autoload': 'true',
        'count': '20',
        'cur_tab': 3
    }
    url = 'https://www.toutiao.com/search_content/?'+urlencode(data)
    try:
        response = requests.get(url)
        if response.status_code == 200:
            return response.text
        return None
    except RequestException:
        print("請求索引頁錯誤")
        return None

def main():
    html=get_page_index()
    print(html)

if __name__ == '__main__':
    main()

2、解析索引頁,因為html返回的是json格式的字串物件,json.loads載入json格式檔案。判斷data[‘data’]鍵是否存在,若存在,返回’article_url’的值

def parse_page_index(html):
    data = json.loads(html)
    if data and 'data' in data.keys():
        for item in data.get('data'):
            yield item.get('article_url')
然後在main函式中呼叫解析頁的url

for url in parse_page_index(html):
    print(url)
3、獲取詳情頁程式碼

ef get_page_detail(url):
    try:
        response = requests.get(url)
        if response.status_code == 200:
            return response.text
        return None
    except RequestException:
        print("請求詳情頁錯誤",url)
        return None
4、解析詳情頁資料。

分析詳情頁原始碼

檢視詳情頁的網站原始碼,發現圖片集的url都在gallery鍵的值中。

首先,獲取每個圖片集的標題title,用select選擇title標籤下的文字

def parse_page_detail(html):
    soup = BeautifulSoup(html,'lxml')
    title = soup.select('title')[0].get_text()
    print(title)
在main函式中判斷html是否正確,返回結果

main():
    html = get_page_index(0,'街拍')
    for url in parse_page_index(html):
        html = get_page_detail(url)
        if html:
            parse_page_detail(html)
獲取每個圖片集中的圖片資訊,所有圖片資訊都在gallery鍵的值中,通過re.comlile正規表示式解析,然後用search得到結果,因為此時得到的結果中資訊不正確,有很多多餘的反斜槓’\’,於是利用replace去掉斜槓。

獲取到的結果為

image_pattern = re.compile('gallery: JSON.parse[(]"(.*?)"[)],\n',re.S)
result = re.search(image_pattern,html)
if result:
    result = result.group(1).replace('\\','')
    print(result)
結果是json字串的格式,需要用loads解析,提取其中的每張照片的url,最後返回的是圖集的標題、連結和每張圖片的url

data=json.loads(result)
if data and 'sub_images' in data.keys():
    sub_images=data.get('sub_images')
    images_url=[item['url'] for item in sub_images]
    return {
        'title':title,
        'url':url,
        'images_url':images_url
    }
輸出的結果為:

此時,所有的資訊已經提取完畢,開始儲存資料

5、把資料儲存到mongodb資料庫中,首先在同一目錄下,建立配置檔案config.py,

MONGO_URL='localhost' #連結地址
MONGO_DB='toutiao'    #資料庫
MONGO_TABLE='toutiao'    #資料集即表
通過from config import *呼叫該檔案

#宣告mongodb資料庫物件
client=pymongo.MongoClient(MONGO_URL)
db=client[MONGO_DB]
然後定義函式儲存到資料庫中,並判斷如果儲存成功輸出相應資訊

def save_to_mongo(result):
    if db[MONGO_TABLE].insert(result):
        print("儲存到mongodb成功",result)
        return True
    return False
6、接下來是根據圖片連結下載圖片

首先定義一個函式,利用pathlib庫根據傳入的目錄名建立一個檔案目錄

def create_dir(name):
    #根據傳入的目錄名建立一個目錄,這裡用到了 python3.4 引入的 pathlib 。
    directory = Path(name)
    if not directory.exists():
        directory.mkdir()
    return directory
然後定義下載圖片函式,要求返回的是content,是二進位制檔案

def download_image(save_dir,url):
    print("正在下載:",url)
    try:
        response = requests.get(url)
        if response.status_code == 200:
            #呼叫儲存圖片函式,返回二進位制
            save_image(save_dir,response.content)
        return None
    except RequestException:
        print("請求圖片出錯",url)
        return None
定義儲存圖片函式

def save_image(save_dir,content):
    '''把檔案儲存到本地,檔案有三部分內容(路徑)/(檔名).(字尾)
    用format構造字串(專案路徑,檔名,格式),md5檔名可以避免重複'''
    #os.getcwd()程式同目錄
    #file_path='{0}/{1}.{2}'.format(os.getcwd(),md5(content).hexdigest(),'jpg')
    file_path = '{0}/{1}.{2}'.format(save_dir, md5(content).hexdigest(), 'jpg')
    #如果檔案不存在,開始存入
    if not os.path.exists(file_path):
        with open(file_path,'wb') as f:
            f.write(content)
            f.close()
在parse_page_detail函式中,呼叫download_image

root_dir = create_dir('D:\spider\jiepai')  # 儲存圖片的根目錄
download_dir = create_dir(root_dir / title)  # 根據每組圖片的title標題名建立目錄
for image in images:
    download_image(download_dir, image)    #下載所有的圖片  
 

 

 

三.代理處理反爬抓取微信公眾號文章,將結構化資料儲存到MongoDB.

 

搜狗(http://weixin.sogou.com/)已經為我們做了一層微信文章的爬取,通過它我們可以獲取一些微信文章的列表以及微信公眾號的一些資訊,但是它有很多反爬蟲的措施,可以檢測到你的IP異常,然後把你封掉。本文采用代理的方法處理反爬來抓取微信文章。

(1)目標站點分析
開啟搜狗微信,輸入要查詢的內容,比如我們輸入“風景”,就會出現微信文章的列表,向下翻動我們可以發現每頁有10條內容,在最下方可以進行翻頁。需要注意的是,未登陸時最多可以檢視10頁內容,登陸之後就可以檢視100頁的內容(也就是說,做爬蟲的時候可以使用cookie爬取到100頁內容):

從網頁的url可以看出這是一個get請求,只保留主要的請求引數,把url簡化為:

其中,“query”代表搜尋的關鍵詞,“type”代表搜尋結果的型別,“type=1”表示搜尋結果是微信公眾號,“type=2”表示搜尋結果是微信文章,“page”也就是當前頁數。

現在網頁是能正常訪問的,但當我們點選翻頁比較頻繁的話,就會出現訪問出錯,輸入驗證碼之後才能再次正常訪問:

對網頁請求進行分析,可以發現正常請求時狀態碼都是200,出現訪問出錯時狀態碼變成了302:

狀態碼302又代表什麼呢?百度一下:

HTTP狀態碼302表示臨時性重定向,該狀態碼錶示請求的資源已被分配了新的URI,希望使用者(本次)能使用新的URI訪問。

也就是說,我們原來的請求被跳轉到了一個新的頁面,這個新的頁面就是訪問出錯,需要輸入驗證碼的頁面:

根據以上分析,我們可以根據請求返回的狀態碼判斷IP是否被封,如果狀態碼是200說明可以正常訪問,如果狀態碼是302,則說明IP已經被封。

接著我們再分析一下後續的頁面。在列表裡開啟一條微信文章,我們可以獲取到文章的標題、公眾號的一些資訊,以及文章內容。文章內容包括文字和一些圖片,在這裡我們只爬取文章中的文字部分。

 
(2)流程框架
1.抓取索引頁內容
利用requests請求目標站點,得到索引網頁HTML程式碼,返回結果。

2.代理設定
如果遇到302狀態碼,則說明IP被封,切換代理重試。

3.分析詳情頁內容
請求詳情頁,分析得到標題、正文等內容。

4.將資料儲存到資料庫
將結構化資料儲存至MongoDB。

 

(3)爬蟲程式碼
# weixin_article.py
 
import re
import requests
from urllib.parse import urlencode
from pyquery import PyQuery as pq
from requests.exceptions import ConnectionError
from weixin_article_config import *
import pymongo
from fake_useragent import UserAgent, FakeUserAgentError
import time
 
client = pymongo.MongoClient(MONGO_URL)
db = client[MONGO_DB]
 
proxy = None  # 全域性代理
 
 
def get_proxy():
    try:
        response = requests.get('http://127.0.0.1:5000/get')
        if response.status_code == 200:
            return response.text
        return None
    except ConnectionError:
        return None
 
 
def get_html(url, count=1):
    print('Crawling', url)
    print('Trying Count', count)
    global proxy  # 引用全域性變數
    if count >= MAX_COUNT:
        print('Tried Too Many Counts')
        return None
    try:
        ua = UserAgent()
    except FakeUserAgentError:
        pass
    headers = {
        'Cookie': 'IPLOC=CN3203; SUID=4761C3782E08990A000000005B496B2A; SUV=1531538220705353; ABTEST=0|1531538246|v1; weixinIndexVisited=1; JSESSIONID=aaa5HdENmLh-idG2g6isw; PHPSESSID=ctkdrtb5sai55cpcd99uglsit4; SUIR=6D964C6E1B1E6F17ABEAE3C31C02C20B; ppinf=5|1533879269|1535088869|dHJ1c3Q6MToxfGNsaWVudGlkOjQ6MjAxN3x1bmlxbmFtZTo1OnN1Z2FyfGNydDoxMDoxNTMzODc5MjY5fHJlZm5pY2s6NTpzdWdhcnx1c2VyaWQ6NDQ6bzl0Mmx1QXg2cUNnY3dESUlCVkQ4REQzejdmVUB3ZWl4aW4uc29odS5jb218; pprdig=qmTYp6UdjuqBQsh41S2iPuC6aVGbF8-gC-oD4_JnXCEABwuE8dKqwUYrBA6ShYaZPxoNW11zcC9vnHQXr57mKAkCYl_j7HUAwAwPPkB8Hw8Iv1IBDQKe3oFFlmig9sp8N_H9VHyF9G-o03WmzDLDoZyiZ-3MPvM-olyDPQ4j_gY; sgid=31-36487535-AVttIibUibd0Gctt8SOaDTEqo; sct=4; wP_h=effa146bd88671d4ec8690f70eefe1957e085595; ppmdig=1533917694000000a1a24383d97adaba971d77d02f75fa59; SNUID=9028BCC8F9FC8BEEF42ED05AF94BDD9E; seccodeRight=success; successCount=1|Fri, 10 Aug 2018 16:20:22 GMT',
        'Host': 'weixin.sogou.com',
        'Referer': 'http://weixin.sogou.com',
        'Upgrade-Insecure-Requests': '1',
        'User-Agent': ua.random
    }
    try:
        if proxy:
            proxies = {'http': proxy}
            response = requests.get(url, allow_redirects=False, headers=headers, proxies=proxies)  # timeout=10
        else:
            response = requests.get(url, allow_redirects=False, headers=headers)  # timeout=10
        if response.status_code == 200:
            return response.text
        if response.status_code == 302:
            # Need Proxy
            print('302')
            proxy = get_proxy()
            if proxy:
                print('Using Proxy', proxy)
                return get_html(url)
            else:
                print('Get Proxy Failed')
                return None
        else:
            print('Error Status Code', response.status_code)
            return None
    except ConnectionError as e:
        print('Error Occurred', e.args)
        proxy = get_proxy()
        count += 1
        return get_html(url, count)
 
 
def get_index(keyword, page):
    data = {
        'query': keyword,
        'type': '2',
        'page': page
    }
    url = 'http://weixin.sogou.com/weixin?' + urlencode(data)
    return get_html(url)
 
 
def parse_index(html):
    doc = pq(html)
    items = doc('.news-box .news-list li .txt-box h3 a').items()
    for item in items:
        yield item.attr('href')
 
 
def get_detail(url):
    try:
        response = requests.get(url)
        if response.status_code == 200:
            return response.text
        return None
    except ConnectionError:
        return None
 
 
def parse_detail(html, url):
    doc = pq(html)
    title = doc('.rich_media_title').text()
    content = "".join(doc('.rich_media_content').text().split())
    nickname = doc('.profile_nickname').text()
    wechat = doc('#js_profile_qrcode > div > p:nth-child(3) > span').text()
    date_pattern = re.compile('var publish_time = \"(.*?)\"', re.S)
    date_search = re.search(date_pattern, html)
    if date_search:
        date = date_search.group(1)
    else:
        date = None
    return {
        'url': url,
        'title': title,
        'date': date,
        'nickname': nickname,
        'wechat': wechat,
        'content': content
    }
 
 
def save_to_mongo(data):
    if db['articles'].update({'title': data['title']}, {'$set': data}, True):
        print('Save to Mongo', data['title'])
        return True
    else:
        print('Save to Mongo Failed', data['title'])
        return False
 
 
def main():
    for page in range(1, 101):
        html = get_index(KEYWOED, page)
        time.sleep(1)  # 減小IP被封的風險
        if html:
            article_urls = parse_index(html)
            for article_url in article_urls:
                if article_url:
                    article_html = get_detail(article_url)
                    if article_html:
                        article_data = parse_detail(article_html, article_url)
                        if article_data:
                            save_to_mongo(article_data)
 
 
if __name__ == '__main__':
    main()
# config.py
 
MONGO_URL = 'localhost'
MONGO_DB = 'weixin'
MONGO_TABLE = 'articles'
 
KEYWOED = '風景'  # 關鍵詞
MAX_COUNT = 5  # 最大請求次數
此外,需要注意的是,程式使用的代理是從自己維護的代理池中取用的,而代理池是從網上獲取的一些公共IP,這些IP一般是不太穩定的,也有可能是很多人都在使用的,所以爬取效果可能不是很好,被禁的概率比較高。當然,你也可以使用自己購買的一些代理,只需要改寫“get_proxy”方法就ok了。

最後,還需要注意Cookies的一些設定。從下面的圖中我們可以看出,cookies是有它自己的過期時間的,如果在抓取過程中發現無法獲取後面的內容,有可能是cookies已經過期了,這時候我們重新登入一下,然後替換掉原來的Cookies就好了。


--------------------- 

原文:https://blog.csdn.net/polyhedronx/article/details/81561456?utm_source=copy 

四.使用Selenium模擬瀏覽器抓取淘寶商品美食資訊.

1.相關工具介紹

  • Selenium是一個自動化測試工具,利用它可以驅動瀏覽器執行特定的動作,如點選、下拉等操作,同時還可以獲取瀏覽器當前呈現的頁面的原始碼,做到可見即可爬。對於一些JavaScript動態渲染的頁面來說,此種抓取方式非常有效。
  • PhanttomJS是無介面瀏覽器,因為老是開著瀏覽器,不方便.
  • Chrome是個瀏覽器

2.目標站點分析

本節中,我們要利用Selenium抓取淘寶商品並用pyquery解析得到商品的圖片、名稱、價格、購買人數、店鋪名稱和店鋪所在地資訊,並將其儲存到MongoDB。

  • 開啟淘寶網:https://www.taobao.com/
  • 搜尋一個關鍵詞:美食,看到非常好多吃的,我們現在就是要爬取這些內容.
  • 開啟淘寶頁面,搜尋商品,比如美食,此時開啟開發者工具,截獲Ajax請求,我們可以發現獲取商品列表的介面,如圖7-19所示。但是這個Ajax介面包含幾個引數,引數不能直接發現其規律,如果要去探尋它的生成規律,也不是做不到,但這樣相對會比較煩瑣,所以如果直接用Selenium來模擬瀏覽器的話,就不需要再關注這些介面引數了,只要在瀏覽器裡面可以看到的,都可以爬取。這也是我們選用Selenium爬取淘寶的原因

     

  • 爬取的順序是:需要模擬在輸入框輸入關鍵詞,點選搜尋按鈕,獲取首頁的內容,模擬點選翻頁,獲得每頁原始碼,並分析商品資訊,儲存到MongoDB資料庫.

4.1宣告瀏覽器物件

4.1.1本次案例宣告瀏覽器方法

from selenium import webdriver
browser = webdriver.Chrome()

Chrome瀏覽器會自動開啟,如下截圖,則說明驅動Chrome瀏覽器成功:

 

4.1.2宣告瀏覽器擴充學習內容

Selenium支援非常多的瀏覽器,如Chrome、Firefox、Edge等,還有Android、BlackBerry等手機端的瀏覽器。另外,也支援無介面瀏覽器PhantomJS。

此外,我們可以用如下方式初始化:


browser = webdriver.Chrome()
browser = webdriver.Firefox()
browser = webdriver.Edge()
browser = webdriver.PhantomJS()
browser = webdriver.Safari()

這樣就完成了瀏覽器物件的初始化並將其賦值為browser物件。接下來,我們要做的就是呼叫browser物件,讓其執行各個動作以模擬瀏覽器操作。

4.2定義搜尋的方法

4.2.1定義搜尋的方法-search()

  • 這裡首先構造了一個WebDriver物件,使用的瀏覽器是Chrome,然後指定一個關鍵詞,如美食.
  • 等待載入時,我們使用了WebDriverWait物件,它可以指定等待條件,同時指定一個最長等待時間,這裡指定為最長10秒。如果在這個時間內成功匹配了等待條件,也就是說頁面元素成功載入出來了,就立即返回相應結果並繼續向下執行,否則到了最大等待時間還沒有載入出來時,就直接丟擲超時異常。
  • 比如,我們最終要等待商品資訊載入出來,就指定了presence_of_element_located這個條件,然後傳入了#q這個選擇器,而這個選擇器對應的內容就是搜尋商品的輸入框.
  • 關於搜尋美食操作,這裡首先獲取商品輸入框,賦值為input,然後獲取“確定”按鈕,賦值為submit.
  • 呼叫send_keys()方法將美食填充到輸入框中,然後點選“確定”按鈕(submit.click())即可。
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

browser = webdriver.Chrome()
wait = WebDriverWait(browser, 10)

def search():
    browser.get("https://www.taobao.com/")
    #等待瀏覽器的載入,需要一點時間,判斷瀏覽器是否載入成功的方法,才進行下面的操作
    input = wait.until(
        EC.presence_of_element_located((By.CSS_SELECTOR, "#q"))#目標是輸入框
    )
    submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#J_TSearchForm > div.search-button > button")))
    #按鈕是可以點選的
    input.send_keys("美食") #操作動作
    submit.click()
    
def main():
    search()
    
    
if __name__=='__main__':
    main()

4.2.2selenium的擴充學習內容

  • 官方學習資料:Selenium with Python
  • Python3網路爬蟲開發實戰7.1-Selenium的使用
  • 顯式等待
    這裡還有一種更合適的顯式等待方法,它指定要查詢的節點,然後指定一個最長等待時間。如果在規定時間內載入出來了這個節點,就返回查詢的節點;如果到了規定時間依然沒有載入出該節點,則丟擲超時異常。示例如下:
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
 
browser = webdriver.Chrome()
browser.get('https://www.taobao.com/')
wait = WebDriverWait(browser, 10)
input = wait.until(EC.presence_of_element_located((By.ID, 'q')))
button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.btn-search')))
print(input, button)

這裡首先引入WebDriverWait這個物件,指定最長等待時間,然後呼叫它的until()方法,傳入要等待條件expected_conditions。比如,這裡傳入了presence_of_element_located這個條件,代表節點出現的意思,其引數是節點的定位元組,也就是ID為q的節點搜尋框。

這樣可以做到的效果就是,在10秒內如果ID為q的節點(即搜尋框)成功載入出來,就返回該節點;如果超過10秒還沒有載入出來,就丟擲異常。

對於按鈕,可以更改一下等待條件,比如改為element_to_be_clickable,也就是可點選,所以查詢按鈕時查詢CSS選擇器為.btn-search的按鈕,如果10秒內它是可點選的,也就是成功載入出來了,就返回這個按鈕節點;如果超過10秒還不可點選,也就是沒有載入出來,就丟擲異常。

執行程式碼,在網速較佳的情況下是可以成功載入出來的。

4.3獲取總頁面的頁數內容-research()

4.3.1本次案例模擬翻頁的方法

  • 首先獲取頁數有多少頁,點選美食搜尋之後,下一步肯定是要等待頁數載入出來,需要再加一個等待的判斷,即
  • 那麼,怎樣知道有沒有跳轉到對應的頁碼呢?我們可以注意到,成功跳轉某一頁後,頁碼都會高亮顯示,如圖7-25所示。total=wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.total")))

     

     

    這裡商品的搜尋結果一般最大都為100頁,要獲取每一頁的內容,只需要將頁碼從1到100順序遍歷即可,頁碼數是確定的。所以,直接在頁面跳轉文字框中輸入要跳轉的頁碼,然後點選“確定”按鈕即可跳轉到頁碼對應的頁面。

  • 使用wait時間過久會出現異常.因此增加try and except:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC



browser = webdriver.Chrome()
wait = WebDriverWait(browser, 10)

def search():
    try:
        browser.get("https://www.taobao.com/")
        #等待瀏覽器的載入,需要一點時間,判斷瀏覽器是否載入成功的方法,才進行下面的操作
        input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#q")))#目標是輸入框
        submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#J_TSearchForm > div.search-button > button")))
        #按鈕是可以點選的
        input.send_keys("美食") #操作動作
        submit.click()
        total = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.total")))
        return total.text#返回內容
    except TimeoutException:
        return search()                                         
                                               
def main():
    total=search()
    print(total)
    
if __name__=='__main__':
    main()

最終顯示的結果:

 

  • 需要用正規表示式提取100
    total=search()
    total=int(re.compile('(\d+)').search(total).group(1))#列印出來可能是個字串,所以要轉義為整數
import re
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC



browser = webdriver.Chrome()
wait = WebDriverWait(browser, 10)

def search():
    try:
        browser.get("https://www.taobao.com/")
        #等待瀏覽器的載入,需要一點時間,判斷瀏覽器是否載入成功的方法,才進行下面的操作
        input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#q")))#目標是輸入框
        submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#J_TSearchForm > div.search-button > button")))
        #按鈕是可以點選的
        input.send_keys("美食") #操作動作
        submit.click()
        total = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.total")))
        return total.text#返回內容
    except TimeoutException:
        return search()                                         
                                               
def main():
    total=search()
    total=int(re.compile('(\d+)').search(total).group(1))
    print(total)
    
if __name__=='__main__':
    main()

最終顯示的結果:

 

4.3.2獲得總頁面頁數的學習內容

  • 獲取SELECTOR的目標值方法

     

4.4迴圈-遍歷每頁-next_page(page_number)

-剛才我們所定義的next_page(page_number)方法需要接收引數page,page代表頁碼。這裡我們實現頁碼遍歷即可.

  • 其實現非常簡單,只需要呼叫一個for迴圈即可。這裡定義最大的頁碼數為100,range()方法的返回結果就是1到100的列表,順序遍歷,呼叫next_page(page_number)方法即可。
  • 這樣我們的淘寶商品爬蟲就完成了,最後呼叫main()方法即可執行。

4.4.1本次迴圈的方法

1)首先檢視淘寶頁面,使用頁面跳轉的方法有兩種,一是使用高亮的第1頁,第2頁,....下一頁,而是直接輸入第幾頁
-方法一 ,不推薦使用,具體原因如下:
這裡不直接點選“下一頁”的原因是:一旦爬取過程中出現異常退出,比如到50頁退出了,此時點選“下一頁”時,就無法快速切換到對應的後續頁面了。此外,在爬取過程中,也需要記錄當前的頁碼數,而且一旦點選“下一頁”之後頁面載入失敗,還需要做異常檢測,檢測當前頁面是載入到了第幾頁。整個流程相對比較複雜,所以這裡我們直接用跳轉的方式來爬取頁面。

  • 方法二,使用的方式是,直接在頁面上寫第幾頁.

     

2)頁面輸入框

input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#mainsrp-pager > div > div > div > div.form > input")))

3)頁面確定按鈕

    submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.form > span.btn.J_Submit")))

 

4)首先清除頁面輸入框的內容:
input.clear()#清除頁碼輸入框的內容

完整的程式碼:

def next_page(page_number):
    print("正在翻頁",page_number)
    try:
        input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#mainsrp-pager > div > div > div > div.form > input")))#目標是輸入框
        submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.form > span.btn.J_Submit")))
        input.clear()#清除頁碼輸入框的內容
        input.send_keys(page_number)#輸入頁碼
        submit.click()
        wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > ul > li.item.active > span"),str(page_number)))#判斷是否翻頁成功,通過判斷當前的頁數是否正確
        get_product()
    except TimeoutException:
        next_page(page_number)

4.5解析網頁-商品列表-get_products()

  • 接下來,我們就可以實現get_products()方法來解析商品列表了。這裡我們直接獲取頁面原始碼,然後用pyquery進行解析
  • 首先,呼叫page_source屬性獲取頁碼的原始碼,然後構造了PyQuery解析物件,接著提取了商品列表,此時使用的CSS選擇器是#mainsrp-itemlist .items .item,它會匹配整個頁面的每個商品。它的匹配結果是多個,所以這裡我們又對它進行了一次遍歷,用for迴圈將每個結果分別進行解析,每次迴圈把它賦值為item變數,每個item變數都是一個PyQuery物件,然後再呼叫它的find()方法,傳入CSS選擇器,就可以獲取單個商品的特定內容了。
  • 可以發現,它是一個img節點,包含id、class、data-src、alt和src等屬性。這裡之所以可以看到這張圖片,是因為它的src屬性被賦值為圖片的URL。把它的src屬性提取出來,就可以獲取商品的圖片了。不過我們還注意data-src屬性,它的內容也是圖片的URL,觀察後發現此URL是圖片的完整大圖,而src是壓縮後的小圖,所以這裡抓取data-src屬性來作為商品的圖片。
  • 因此,我們需要先利用find()方法找到圖片的這個節點,然後再呼叫attr()方法獲取商品的data-src屬性,這樣就成功提取了商品圖片連結。然後用同樣的方法提取商品的價格、成交量、名稱、店鋪和店鋪所在地等資訊,接著將所有提取結果賦值為一個字典product,隨後呼叫save_to_mongo()將其儲存到MongoDB即可。
  1. 儲存到MongoDB

4.5.1本次解析的方法

  • 商品的程式碼結構,如下:

     

  • 判斷頁面商品資訊是否載入成功
    wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-itemlist .items .item ")))

  • 獲得頁面所有選擇的內容:
    items=doc('#mainsrp-itemlist .items .item').items()

  • 分析商品圖片:
    "image" :item.find(".pic .img").attr("src")

     

  • 解析商品價格
    "price" :item.find(".price").text()

     

  • 解析商品成交量
    "deal" :item.find(".deal-cnt").text()[:-3]切片到倒數第三個

     

  • 解析商品標題
    "title" :item.find(".title").text()

     

  • 解析商品店鋪名稱
    "shop" :item.find(".shop").text()

     

  • 解析商品店鋪地址
    "location" :item.find(".location").text()

     

     

    -完整的程式碼:

# -*- coding: utf-8 -*-
"""
Created on Sat Feb 10 18:33:26 2018

@author: Administrator
"""
import re
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from pyquery import PyQuery as pq


browser = webdriver.Chrome()
wait = WebDriverWait(browser, 10)

def search():
    try:
        browser.get("https://www.taobao.com/")
        #等待瀏覽器的載入,需要一點時間,判斷瀏覽器是否載入成功的方法,才進行下面的操作
        input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#q")))#目標是輸入框
        submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#J_TSearchForm > div.search-button > button")))
        #確定按鈕
        input.send_keys("美食") #操作動作
        submit.click()
        total = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.total")))
        get_product()
        return total.text#返回內容
    except TimeoutException:
        return search()          


def next_page(page_number):
    try:
        input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#mainsrp-pager > div > div > div > div.form > input")))#目標是輸入框
        submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.form > span.btn.J_Submit")))
        input.clear()#清除頁碼輸入框的內容
        input.send_keys(page_number)#輸入頁碼
        submit.click()
        wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > ul > li.item.active > span"),str(page_number)))#判斷是否翻頁成功,通過判斷當前的頁數是否正確
        get_product()
    except TimeoutException:
        next_page(page_number)

def get_product():
    wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-itemlist .items .item")))#判斷商品資訊是否載入成功
    html = browser.page_source#獲得網頁原始碼
    doc=pq(html)
    items=doc('#mainsrp-itemlist .items .item').items()
    for item in items:
         product = {
                "image" :item.find(".pic .img").attr("src"),
                "price" :item.find(".price").text(),
                "deal" :item.find(".deal-cnt").text()[:-3],#切片到倒數第三個
                "title" :item.find(".title").text(),
                "shop" :item.find(".shop").text(),
                "location" :item.find(".location").text(),
                 }                    
         print(product)
                                               
def main():
    total=search()
    total=int(re.compile('(\d+)').search(total).group(1))
    for i in range(2,total+1):
        next_page(i)
    
if __name__=='__main__':
    main()

-最後顯示的結果

 

4.5.2解析的學習內容

4.6儲存在MongoDB

這裡首先建立了一個MongoDB的連線物件,然後指定了資料庫,隨後指定了Collection的名稱,接著直接呼叫insert()方法將資料插入到MongoDB。此處的result變數就是在get_products()方法裡傳來的product,包含單個商品的資訊。

4.6.1本次儲存在MongoDB的方法

  • 新建一個配置檔案叫untitled2.py,並輸入以下程式碼
MONGO_URL ="localhost"#本地資料庫
MONGO_DB="taobao"#資料庫的名稱
MONGO_TABLE="product"#資料庫表的名稱
  • 在之前的檔案引入mongodb相關資料
import re
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from pyquery import PyQuery as pq
from untitled2 import *#引入mongodb的配置檔案
import pymongo#引入mongodb所有的變數
  • 在之前的檔案宣告mongodb相關資訊
client=pymongo.MongoClient(MONGO_URL)
db=client[MONGO_DB]
  • 在之前的檔案定義mongodb方法:
def save_to_mongo(result):
    try:
        if db[MONGO_TABLE].insert(result):
            print("儲存到MONGODB成功",result)
    except Exception:
        print("儲存到MONGODB失敗",result)
  • 完整的程式碼為:
import re
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from pyquery import PyQuery as pq
from untitled2 import *#引入mongodb的配置檔案
import pymongo

client=pymongo.MongoClient(MONGO_URL)
db=client[MONGO_DB]


browser = webdriver.Chrome()
wait = WebDriverWait(browser, 10)

def search():
    try:
        browser.get("https://www.taobao.com/")
        #等待瀏覽器的載入,需要一點時間,判斷瀏覽器是否載入成功的方法,才進行下面的操作
        input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#q")))#目標是輸入框
        submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#J_TSearchForm > div.search-button > button")))
        #確定按鈕
        input.send_keys("美食") #操作動作
        submit.click()
        total = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.total")))
        get_product()
        return total.text#返回內容
    except TimeoutException:
        return search()          


def next_page(page_number):
    try:
        input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#mainsrp-pager > div > div > div > div.form > input")))#目標是輸入框
        submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.form > span.btn.J_Submit")))
        input.clear()#清除頁碼輸入框的內容
        input.send_keys(page_number)#輸入頁碼
        submit.click()
        wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > ul > li.item.active > span"),str(page_number)))#判斷是否翻頁成功,通過判斷當前的頁數是否正確
        get_product()
    except TimeoutException:
        next_page(page_number)

def get_product():
    wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-itemlist .items .item")))#判斷商品資訊是否載入成功
    html = browser.page_source#獲得網頁原始碼
    doc=pq(html)
    items=doc('#mainsrp-itemlist .items .item').items()
    for item in items:
         product = {
                "image" :item.find(".pic .img").attr("src"),
                "price" :item.find(".price").text(),
                "deal" :item.find(".deal-cnt").text()[:-3],#切片到倒數第三個
                "title" :item.find(".title").text(),
                "shop" :item.find(".shop").text(),
                "location" :item.find(".location").text(),
                 }                    
         print(product)
         save_to_mongo(product)

def save_to_mongo(result):
    try:
        if db[MONGO_TABLE].insert(result):
            print("儲存到MONGODB成功",result)
    except Exception:
        print("儲存到MONGODB失敗",result)

def main():
    total=search()
    total=int(re.compile('(\d+)').search(total).group(1))
    for i in range(2,total+1):
        next_page(i)
    browser.close()#把瀏覽器關掉
    
if __name__=='__main__':
    main()

配置檔案

MONGO_URL ="localhost"
MONGO_DB="taobao"
MONGO_TABLE="product"

執行的時候,同時要將配置檔案開啟,才可以執行.執行的結果如下:

 

 

4.6.2MongoDB的學習內容

4.7使用PhantomJS

4.7.1本次使用PhantomJS的方法

  • 因用模擬瀏覽器開啟,有點麻煩,所以改為用PhanttomJS,,是無介面瀏覽器.
    將browser = webdriver.Chrome()改為browser = webdriver.PhantomJS()
  • 在untitled2.py配置檔案增加:
    SERVICE_ARGS=["--load-images=false","--disk-cache=true"]#是陣列形式,不載入瀏覽器圖片,開啟快取
  • 在之前檔案,增加配置文的變數引入:
    browser = webdriver.PhantomJS(service_args=SERVICE_ARGS)
  • 分別在search()/next_page(page_number)分別增加print("正在搜尋")/print("正在翻頁",page_number)
  • 完整的程式碼為:
# -*- coding: utf-8 -*-
"""
Created on Sat Feb 10 18:33:26 2018

@author: Administrator
"""
import re
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from pyquery import PyQuery as pq
from untitled2 import *#引入mongodb的配置檔案
import pymongo

client=pymongo.MongoClient(MONGO_URL)
db=client[MONGO_DB]


browser = webdriver.PhantomJS(service_args=SERVICE_ARGS)
wait = WebDriverWait(browser, 10)

browser.set_window_size(1400,900)#設定視窗大小

def search():
    print("正在搜尋")
    try:
        browser.get("https://www.taobao.com/")
        #等待瀏覽器的載入,需要一點時間,判斷瀏覽器是否載入成功的方法,才進行下面的操作
        input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#q")))#目標是輸入框
        submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#J_TSearchForm > div.search-button > button")))
        #確定按鈕
        input.send_keys("美食") #操作動作
        submit.click()
        total = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.total")))
        get_product()
        return total.text#返回內容
    except TimeoutException:
        return search()          


def next_page(page_number):
    print("正在翻頁",page_number)
    try:
        input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#mainsrp-pager > div > div > div > div.form > input")))#目標是輸入框
        submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.form > span.btn.J_Submit")))
        input.clear()#清除頁碼輸入框的內容
        input.send_keys(page_number)#輸入頁碼
        submit.click()
        wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > ul > li.item.active > span"),str(page_number)))#判斷是否翻頁成功,通過判斷當前的頁數是否正確
        get_product()
    except TimeoutException:
        next_page(page_number)

def get_product():
    wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-itemlist .items .item")))#判斷商品資訊是否載入成功
    html = browser.page_source#獲得網頁原始碼
    doc=pq(html)
    items=doc('#mainsrp-itemlist .items .item').items()
    for item in items:
         product = {
                "image" :item.find(".pic .img").attr("src"),
                "price" :item.find(".price").text(),
                "deal" :item.find(".deal-cnt").text()[:-3],#切片到倒數第三個
                "title" :item.find(".title").text(),
                "shop" :item.find(".shop").text(),
                "location" :item.find(".location").text(),
                 }                    
         print(product)
         save_to_mongo(product)

def save_to_mongo(result):
    try:
        if db[MONGO_TABLE].insert(result):
            print("儲存到MONGODB成功",result)
    except Exception:
        print("儲存到MONGODB失敗",result)

                                               
def main():
    total=search()
    total=int(re.compile('(\d+)').search(total).group(1))
    for i in range(2,total+1):
        next_page(i)
    browser.close()#把瀏覽器關掉
    
if __name__=='__main__':
    main()

配置檔案程式碼為:

MONGO_URL ="localhost"
MONGO_DB="taobao"
MONGO_TABLE="product"


SERVICE_ARGS=["--load-images=false","--disk-cache=true"]#是陣列形式,不載入瀏覽器圖片,開啟快取

執行的時候,同時要將配置檔案開啟,才可以執行.執行的結果如下:

 

4.7.2使用PhantomJS的方法

官方網站:PhantomJS

4.8整體程式碼的完善

4.7.1本次完善程式碼的方法

  • 在untitled2.py配置檔案增加:KEYWORD = "美食"
    然後將search()中的input.send_keys("美食")改為input.send_keys("KEYWORD")
  • 將main(),增加try 和findally
def main():
    try:
        total=search()
        total=int(re.compile('(\d+)').search(total).group(1))
        for i in range(2,total+1):
            next_page(i)
    finally:
        browser.close()#把瀏覽器關掉

完整的程式碼

@author: Administrator
"""
import re
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from pyquery import PyQuery as pq
from untitled2 import *#引入mongodb的配置檔案
import pymongo

client=pymongo.MongoClient(MONGO_URL)
db=client[MONGO_DB]


browser = webdriver.PhantomJS(service_args=SERVICE_ARGS)
wait = WebDriverWait(browser, 10)

browser.set_window_size(1400,900)#設定視窗大小

def search():
    print("正在搜尋")
    try:
        browser.get("https://www.taobao.com/")
        #等待瀏覽器的載入,需要一點時間,判斷瀏覽器是否載入成功的方法,才進行下面的操作
        input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#q")))#目標是輸入框
        submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#J_TSearchForm > div.search-button > button")))
        #確定按鈕
        input.send_keys("KEYWORD") #操作動作
        submit.click()
        total = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.total")))
        get_product()
        return total.text#返回內容
    except TimeoutException:
        return search()          


def next_page(page_number):
    print("正在翻頁",page_number)
    try:
        input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#mainsrp-pager > div > div > div > div.form > input")))#目標是輸入框
        submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.form > span.btn.J_Submit")))
        input.clear()#清除頁碼輸入框的內容
        input.send_keys(page_number)#輸入頁碼
        submit.click()
        wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > ul > li.item.active > span"),str(page_number)))#判斷是否翻頁成功,通過判斷當前的頁數是否正確
        get_product()
    except TimeoutException:
        next_page(page_number)

def get_product():
    wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-itemlist .items .item")))#判斷商品資訊是否載入成功
    html = browser.page_source#獲得網頁原始碼
    doc=pq(html)
    items=doc('#mainsrp-itemlist .items .item').items()
    for item in items:
         product = {
                "image" :item.find(".pic .img").attr("src"),
                "price" :item.find(".price").text(),
                "deal" :item.find(".deal-cnt").text()[:-3],#切片到倒數第三個
                "title" :item.find(".title").text(),
                "shop" :item.find(".shop").text(),
                "location" :item.find(".location").text(),
                 }                    
         print(product)
         save_to_mongo(product)

def save_to_mongo(result):
    try:
        if db[MONGO_TABLE].insert(result):
            print("儲存到MONGODB成功",result)
    except Exception:
        print("儲存到MONGODB失敗",result)

                                               
def main():
    try:
        total=search()
        total=int(re.compile('(\d+)').search(total).group(1))
        for i in range(2,total+1):
            next_page(i)
    finally:
        browser.close()#把瀏覽器關掉
    
if __name__=='__main__':
    main()

配置檔案:

MONGO_URL ="localhost"
MONGO_DB="taobao"
MONGO_TABLE="product"


SERVICE_ARGS=["--load-images=false","--disk-cache=true"]#是陣列形式,不載入瀏覽器圖片,開啟快取
KEYWORD = "美食"




連結:https://www.jianshu.com/p/cdf916f30d1f
 

 

五.使用Redis+Flask維護動態Cookies池以及動態代理池.

在做爬蟲的時候,可能會遇到IP被封的問題,利用代理就可以偽裝自己的IP進行爬蟲請求。在做爬蟲請求的時候需要很多代理IP,所以我們可以建立一個代理池,對代理池中的IP進行定期的檢查和更新,保證裡面所有的代理都是可用的。這裡我們使用Redis和Flask維護一個代理池,Redis主要用來提供代理池的佇列儲存,Flask是用來實現代理池的一個介面,用它可以從代理池中拿出一個代理,即通過web形式把代理返回過來,就可以拿到可用的代理了。

(1)為什麼要用代理池
許多網站有專門的反爬蟲措施,可能遇到封IP等問題。
網際網路上公開了大量免費代理,要利用好資源。
通過定時的檢測維護同樣可以得到多個可用代理。
(2)代理池的要求
多站抓取,非同步檢測
定時篩選,持續更新
提供介面,易於提取
(3)代理池架構


架構最核心的部分是“代理佇列”,我們要維護的就是這個佇列,裡面存了很多代理,佇列可以用python的資料結構來存,也可以用資料庫來存。維護好佇列我們需要做兩件事情:第一,就是向佇列裡新增一些可用的代理,獲取器從各大網站平臺上把代理抓取下來,臨時存到一個資料結構裡面,然後用過濾器對這些代理進行篩選。篩選的方法也很簡單,拿到代理之後,用它請求百度之類的網站,如果可以正常地請求網站,就說明代理可用,否則就將它剔除。過濾完之後將剩餘可用的代理放入代理佇列。第二,就是對代理佇列進行定時檢測,因為經過一段時間之後,代理佇列裡的部分代理可能已經失效,這就需要定時地從裡面拿出一些代理,重新進行檢測,保留可用的代理,剔除已經失效的代理。最後我們還需要做一個API,通過介面的形式拿到代理佇列裡面的一些代理。

(4)代理池實現
專案參考來源:https://github.com/germey/proxypool

修改後的程式可以直接下載

各模組功能
getter.py

爬蟲模組

class proxypool.getter.FreeProxyGetter

爬蟲類,用於抓取代理源網站的代理,使用者可複寫和補充抓取規則。

schedule.py

排程器模組

class proxypool.schedule.ValidityTester

非同步檢測類,可以對給定的代理的可用性進行非同步檢測。

class proxypool.schedule.PoolAdder

代理新增器,用來觸發爬蟲模組,對代理池內的代理進行補充,代理池代理數達到閾值時停止工作。

class proxypool.schedule.Schedule

代理池啟動類,執行RUN函式時,會建立兩個程式,負責對代理池內容的增加和更新。

db.py

Redis資料庫連線模組

class proxypool.db.RedisClient

資料庫操作類,維持與Redis的連線和對資料庫的增刪查該,

error.py

異常模組

class proxypool.error.ResourceDepletionError

資源枯竭異常,如果從所有抓取網站都抓不到可用的代理資源,則丟擲此異常。

class proxypool.error.PoolEmptyError

代理池空異常,如果代理池長時間為空,則丟擲此異常。

api.py

API模組,啟動一個Web伺服器,使用Flask實現,對外提供代理的獲取功能。

utils.py

工具箱

setting.py

設定

 
使用示例
import os
import sys
import requests
from bs4 import BeautifulSoup
 
dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, dir)
 
 
def get_proxy():
    r = requests.get('http://127.0.0.1:5000/get')
    proxy = BeautifulSoup(r.text, "lxml").get_text()
    return proxy
 
 
def crawl(url, proxy):
    proxies = {'http': proxy}
    r = requests.get(url, proxies=proxies)
    return r.text
 
 
def main():
    proxy = get_proxy()
    html = crawl('http://docs.jinkan.org/docs/flask/', proxy)
    print(html)
 
 
if __name__ == '__main__':
    main()
 
Last but not least:
如果程式執行過程中出現錯誤,很有可能是部分代理網站發生了變化,但“getter.py”檔案裡請求代理網站程式沒有更新導致的。比如有的代理網站不再能夠訪問,或網站不能正常請求,返回503之類的錯誤,就需要對程式作出更改,或者直接去掉不能正常訪問的網站,重新找一些新的可用的代理網站加進去。

另外,這套程式還有一個不足之處,就是代理池中的代理IP很有可能是重複的,而且重複率會隨著執行時間的增加而提高。要解決這個問題,一個是可以增加代理網站的數量,使代理池中的代理有更豐富的來源,此外,還可以在向代理池中增加新的代理時進行重複性檢查,如果代理池中已經有該代理IP,則放棄存入代理池。


PS:針對代理池中的代理IP可能會重複的問題,提出了一種解決方法,實測可行。

代理IP之所以會重複,和Redis資料庫使用的資料結構有很大關係,原程式使用的是列表(list)結構,資料以列表形式存入資料庫後是有序但允許重複的,當有新的資料存入時,並不會對資料的重複性進行檢查和處理。但Redis不僅有列表結構,常見的Redis資料結構有String、Hash、List、Set(集合)和Sorted Set(有序集合),使用Set和Sorted Set結構就不會出現重複元素。

Set是無序集合,元素無序排列,當有重複元素存入時,資料庫是不會發生變化的;Sorted Set是有序集合,有序集合是可排序的,但是它和列表使用索引下標進行排序依據不同的是,它給每個元素設定一個分數(score)作為排序的依據,當存入一個元素時,同時需要存入該元素的分數。

Sorted Set使用起來較複雜,主要是分數分配問題比較難搞,所以這裡使用Set代替原程式中的List作為資料庫的資料結構,將以下內容代替原來的“db.py”檔案中的內容即可:

# db.py
 
import redis
from proxypool.error import PoolEmptyError
from proxypool.setting import HOST, PORT, PASSWORD
 
 
class RedisClient(object):
    def __init__(self, host=HOST, port=PORT):
        if PASSWORD:
            self._db = redis.Redis(host=host, port=port, password=PASSWORD)
        else:
            self._db = redis.Redis(host=host, port=port)
 
    def get(self, count=1):
        """
        get proxies from redis
        """
        proxies = []
        for i in range(count):
            proxies.append(self._db.spop("proxies"))
        return proxies
 
    def put(self, proxy):
        """
        add proxy to right top
        """
        self._db.sadd("proxies", proxy)
 
    def pop(self):
        """
        get proxy from right.
        """
        try:
            return self._db.spop("proxies").decode('utf-8')
        except:
            raise PoolEmptyError
 
    @property
    def queue_len(self):
        """
        get length from queue.
        """
        return self._db.scard("proxies")
 
    def flush(self):
        """
        flush db
        """
        self._db.flushall()
 
 
if __name__ == '__main__':
    conn = RedisClient()
    print(conn.pop())
將資料結構改為Set以後,便不會出現代理池中代理IP重複的問題,但這樣做也是有弊端的,因為Set是無序的,所以更新代理池的過程中每次彈出的代理IP也是隨機的,這樣代理池中的某些代理可能永遠也不會被更新,而我們獲取代理時採用pop方法得到的也是代理池中隨機彈出的代理,該代理有可能是很久沒有被更新的已經失效的代理。

總結一下就是使用Set結構可以保證代理池中的代理不會重複,但不能保證呼叫代理池獲取代理時得到的代理是最新的和可用的,而List結構可以保證當前獲取的代理是最新的,但代理池中的代理可能會有很大的重複。總之,兩種方法都是有利有弊的,當然也可以嘗試用有序集合(Sorted Set)構建一種完美的方法了。

 

參考內容:

python redis-string、list、set操作

Python操作redis學習系列之(集合)set,redis set詳解 (六)

Redis 有序集合

redis學習筆記(三):列表、集合、有序集合
--------------------- 
原文:https://blog.csdn.net/polyhedronx/article/details/81485458?utm_source=copy 

(1)為什麼要用Cookies池?
有些網站需要登入才能爬取,例如新浪微博
爬取過程中如果頻率過高會導致封號
需要維護多個賬號的Cookies池實現大規模爬取
(2)Cookies池的要求
自動登入更新
定時驗證篩選
提供外部介面
(3)Cookies池架構
Cookies池的架構採用下圖所示的形式:

首先,需要有一個賬號佇列,把一些賬號密碼存到資料庫裡,生成器即程式從佇列裡面拿出賬號密碼,自動地進行登入,並獲取登入的Cookies,然後放到Cookies佇列裡。定時檢測器從Cookies佇列裡定期地隨機選出一些Cookies,並用這些Cookies請求網頁,如果請求成功就放回佇列,否則從佇列裡剔除,這樣就能做到實時更新,保證Cookies佇列裡的Cookies都是可用的。此外,還需要提供一個API介面,使外部程式能夠從佇列裡獲取到Cookies。

(4)Cookies池的實現
專案參考來源:

https://github.com/Germey/CookiesPool

https://github.com/Python3WebSpider/CookiesPool

可擴充套件的Cookies池,目前對接了新浪微博,m.weibo.cn,可自行擴充套件其他站點。

安裝
pip3 install -r requirements.txt
基礎配置
介面基本配置
# Redis資料庫地址
REDIS_HOST = 'localhost'
 
# Redis埠
REDIS_PORT = 6379
 
# Redis密碼,如無填None
REDIS_PASSWORD = 'foobared'
 
# 產生器使用的瀏覽器
BROWSER_TYPE = 'Chrome'
 
# 產生器類,如擴充套件其他站點,請在此配置
GENERATOR_MAP = {
    'weibo': 'WeiboCookiesGenerator'
}
 
# 測試類,如擴充套件其他站點,請在此配置
TESTER_MAP = {
    'weibo': 'WeiboValidTester'
}
 
# 檢測器檢測介面
TEST_URL_MAP = {
    'weibo': 'https://m.weibo.cn/api/container/getIndex?uid=1804544030&type=uid&page=1&containerid=1076031804544030'
}
 
# 產生器和驗證器迴圈週期
CYCLE = 120
 
# API地址和埠
API_HOST = '0.0.0.0'
API_PORT = 5000
程式開關
在config.py修改

# 產生器開關,模擬登入新增Cookies
GENERATOR_PROCESS = True
# 驗證器開關,迴圈檢測資料庫中Cookies是否可用,不可用刪除
VALID_PROCESS = True
# API介面服務
API_PROCESS = True
賬號購買
賬號可在淘寶購買。

匯入賬號
python3 importer.py
請輸入賬號密碼組, 輸入exit退出讀入
18459748505----astvar3647
14760253606----gmidy8470
14760253607----uoyuic8427
18459749258----rktfye8937
賬號 18459748505 密碼 astvar3647
錄入成功
賬號 14760253606 密碼 gmidy8470
錄入成功
賬號 14760253607 密碼 uoyuic8427
錄入成功
賬號 18459749258 密碼 rktfye8937
錄入成功
exit
執行
請先匯入一部分賬號之後再執行,執行命令:

python3 run.py
執行效果
三個程式全部開啟:

API介面開始執行
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
Cookies生成程式開始執行
Cookies檢測程式開始執行
正在生成Cookies 賬號 14747223314 密碼 asdf1129
正在測試Cookies 使用者名稱 14747219309
Cookies有效 14747219309
正在測試Cookies 使用者名稱 14740626332
Cookies有效 14740626332
正在測試Cookies 使用者名稱 14740691419
Cookies有效 14740691419
正在測試Cookies 使用者名稱 14740618009
Cookies有效 14740618009
正在測試Cookies 使用者名稱 14747222472
Cookies有效 14747222472
Cookies檢測完成
驗證碼位置 420 580 384 544
成功匹配
拖動順序 [1, 4, 2, 3]
成功獲取到Cookies {'SUHB': '08J77UIj4w5n_T', 'SCF': 'AimcUCUVvHjswSBmTswKh0g4kNj4K7_U9k57YzxbqFt4SFBhXq3Lx4YSNO9VuBV841BMHFIaH4ipnfqZnK7W6Qs.', 'SSOLoginState': '1501439488', '_T_WM': '99b7d656220aeb9207b5db97743adc02', 'M_WEIBOCN_PARAMS': 'uicode%3D20000174', 'SUB': '_2A250elZQDeRhGeBM6VAR8ifEzTuIHXVXhXoYrDV6PUJbkdBeLXTxkW17ZoYhhJ92N_RGCjmHpfv9TB8OJQ..'}
成功儲存Cookies
 
-------------------- 
原文:https://blog.csdn.net/polyhedronx/article/details/81584618?utm_source=copy 
 

六.使用Scrapy+Cookies池抓取新浪微博

一、什麼是Scrapy
Scrapy是Python開發的一個快速、高層次的螢幕抓取和web抓取框架,用於抓取web站點並從頁面中提取結構化的資料。Scrapy用途廣泛,可以用於資料探勘、監測和自動化測試。

Scrapy吸引人的地方在於它是一個框架,任何人都可以根據需求方便的修改。它也提供了多種型別爬蟲的基類,如BaseSpider、sitemap爬蟲等,最新版本又提供了web2.0爬蟲的支援。

 

 

二、直接安裝
下面介紹在Windows下安裝Scrapy的過程。

在Windows下安裝Scrapy框架是非常麻煩的,需要安裝許多依賴庫,萬一有一個依賴庫沒有安裝好就會導致安裝失敗。安裝過程可以分為以下幾步:

1.安裝wheel
pip install wheel
第一步需要安裝wheel,安裝好wheel庫之後,你就可以安裝一些wheel檔案,通過這些wheel檔案完成其他庫的安裝。因為我已經安裝過了,所以會顯示以下內容。

2. 安裝lxml
第二步需要安裝lxml,通過這個連結http://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml,可以找到很多不同版本的wheel檔案,由於我使用的是python3.6,所以就下載了下面這個檔案。

直接點選就可以下載,下載完成後找到該檔案,右鍵-->屬性-->安全,把物件名稱,也就是檔案路徑copy下來,然後在CMD視窗輸入“pip install copy的路徑”就可以了。

3.PyOpenssl
第三步需要安裝PyOpenssl,通過連結https://pypi.python.org/pypi/pyOpenSSL#downloads,進入pypi網站,點選“Download files”,然後點選下載wheel檔案即可。

接著,同樣把檔案路徑(包括檔名稱)複製下來,在CMD輸入“pip install copy的路徑”進行安裝。

4.安裝Twisted
第四步需要安裝Twisted框架,這是一個非同步框架,Scrapy的核心就是基於Twisted框架。同樣地,訪問http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted,下載對應版本的wheel檔案。

下面依然是複製檔案路徑,並用pip進行安裝。

5.安裝Pywin32
接下來需要安裝Pywin32檔案,它是python和win32在執行過程中相容的一個庫。訪問https://sourceforge.net/projects/pywin32/files/pywin32/Build%20220/,開啟之後會看到一個下載列表,這裡點選下載3.6版本。

下載完成會看到一個exe檔案,執行之,依次點選下一步,然後點選完

成。

 

6.安裝Scrapy
安裝完依賴庫之後,最後一步安裝Scrapy框架就Ok了。

pip install scrapy
7.測試Scrapy
最後對安裝完的Scrapy框架進行測試,比如新建一個“hello”專案:輸入“scrapy startproject hello”:

根據提示依次輸入:

cd hello
scrapy genspider baidu www.baidu.com
然後它給我們建立了一個spider:

接下來我們輸入“scrapy crawl baidu”,執行一下這個spider,如果執行過程沒有出錯的話,就說明Scrapy框架安裝沒問題。

 

三、Anaconda平臺下的安裝
Anaconda是一個科學計算環境,它裡面給我們提供了許多庫。如果裝有Anaconda,就可以用“conda”命令安裝Scrapy。

用conda安裝是非常簡單的,只需要輸入“conda install scrapy”就可以了,出現提示後輸入“y”。


--------------------- 
原文:https://blog.csdn.net/polyhedronx/article/details/82461925?utm_source=copy 

使用方法:https://blog.csdn.net/qq_42156420/article/details/80746774

前面講解了Scrapy中各個模組基本使用方法以及代理池、Cookies池。接下來我們以一個反爬比較強的網站新浪微博為例,來實現一下Scrapy的大規模爬取。

一、本節目標

本次爬取的目標是新浪微博使用者的公開基本資訊,如使用者暱稱、頭像、使用者的關注、粉絲列表以及釋出的微博等,這些資訊抓取之後儲存至MongoDB。

二、準備工作

請確保前文所講的代理池、Cookies池已經實現並可以正常執行,安裝Scrapy、PyMongo庫。

三、爬取思路

首先我們要實現使用者的大規模爬取。這裡採用的爬取方式是,以微博的幾個大V為起始點,爬取他們各自的粉絲和關注列表,然後獲取粉絲和關注列表的粉絲和關注列表,以此類推,這樣下去就可以實現遞迴爬取。如果一個使用者與其他使用者有社交網路上的關聯,那他們的資訊就會被爬蟲抓取到,這樣我們就可以做到對所有使用者的爬取。通過這種方式,我們可以得到使用者的唯一ID,再根據ID獲取每個使用者釋出的微博即可。

四、爬取分析

這裡我們選取的爬取站點是:https://m.weibo.cn,此站點是微博移動端的站點。開啟該站點會跳轉到登入頁面,這是因為主頁做了登入限制。不過我們可以繞過登入限制,直接開啟某個使用者詳情頁面,例如開啟周冬雨的微博,連結為:https://m.weibo.cn/u/1916655407,即可進入其個人詳情頁面,如下圖所示

我們在頁面最上方可以看到周冬雨的關注和粉絲數量。我們點選關注,進入到她的關注列表,如下圖所示。

我們開啟開發者工具,切換到XHR過濾器,一直下拉關注列表,即可看到下方會出現很多Ajax請求,這些請求就是獲取周冬雨的關注列表的Ajax請求,如下圖所示。

我們開啟第一個Ajax請求,它的連結為:https://m.weibo.cn/api/container/getIndex?containerid=231051-_followers-_1916655407&luicode=10000011&lfid=1005051916655407&featurecode=20000320&type=uid&value=1916655407&page=2,詳情如下圖所示。

請求型別是GET型別,返回結果是JSON格式,我們將其展開之後即可看到其關注的使用者的基本資訊。接下來我們只需要構造這個請求的引數。此連結一共有7個引數,如下圖所示。

其中最主要的引數就是containeridpage。有了這兩個引數,我們同樣可以獲取請求結果。我們可以將介面精簡為:https://m.weibo.cn/api/container/getIndex?containerid=231051-_followers-_1916655407&page=2,這裡的container_id的前半部分是固定的,後半部分是使用者的id。所以這裡引數就可以構造出來了,只需要修改container_id最後的idpage引數即可獲取分頁形式的關注列表資訊。

利用同樣的方法,我們也可以分析使用者詳情的Ajax連結、使用者微博列表的Ajax連結,如下所示:

# 使用者詳情API
user_url = 'https://m.weibo.cn/api/container/getIndex?uid={uid}&type=uid&value={uid}&containerid=100505{uid}'
# 關注列表API
follow_url = 'https://m.weibo.cn/api/container/getIndex?containerid=231051_-_followers_-_{uid}&page={page}'
# 粉絲列表API
fan_url = 'https://m.weibo.cn/api/container/getIndex?containerid=231051_-_fans_-_{uid}&page={page}'
# 微博列表API
weibo_url = 'https://m.weibo.cn/api/container/getIndex?uid={uid}&type=uid&page={page}&containerid=107603{uid}'
詳情:https://blog.csdn.net/m0_37438418/article/details/80819847

具體操作:http://www.cnblogs.com/bep-feijin/p/9325184.html

 

七.使用Scrapy分散式架構抓取知乎文章

 

https://blog.csdn.net/qq_40717846/article/details/79014132

 

相關文章