1. 程式人生 > >實現一個完整的爬蟲

實現一個完整的爬蟲

需要匯入的包

import random
from random import randint

import requests
from fake_useragent import UserAgent #反爬蟲 偽裝請求頭
from retrying import retry #裝飾器
import hashlib #資訊摘要 md5
import queue #佇列
import re #正則表示式
from urllib import robotparser #解析網站robots.txt檔案
from urllib.parse import urlparse,urljoin,urldefrag #解析url
from threading import Thread #多執行緒
from datetime import datetime
import time
import Mongo_cache

1.定義爬蟲的爬取深度

MAX_DEP = 2

2.有一些站點它在 robots.txt 檔案中設定了禁止流量該站點的代理使用者。既然目標站點有這樣的規矩,我們就要遵循它。
#解析robots.txt檔案,然後在在對需要下載的網頁進行判斷是否可以爬取。

def get_robots(url):
    """
    解析robots.txt檔案
    :param url:
    :return:
    """
    rp = robotparser.RobotFileParser()
    rp.set_url(urljoin(url,'robots.txt'))
    rp.read()
    return rp

3.儲存下載內容
#首先轉換為md5形式存進資料庫.然後得到下載連結的名字,寫入到download檔案

def save_url(html_content,url_str):
    """
    儲存下載內容
    :param html_content:
    :param url_str:
    :return:
    """
    md5 = hashlib.md5()
    md5.update(html_content)
    #file_path = "./download/" + md5.hexigest()+".html"
    file_path = "./download/" + gen_html_name(url_str)+".html"
    with open(file_path,"wb") as f:
        f.write(html_content)

4.根據路徑切割出html名字 urlparse模組主要用於解析url裡面的引數,對url按照一定的格式進行拼接或拆分

def gen_html_name(url_str):
    """
    得到html的名字
    :param url_str:
    :return:
    """
    path = urlparse(url_str).path
    path_array = path.split('/')
    return path_array[len(path_array) - 1]

5.抽取網頁中的其他連結 re.IGNORECASE 表示忽略大小寫.

def extractor_url_lists(html_content):
    """
    抽取網頁中的其他連結
    :param html_content:
    :return:
    """
    #^出現在[]裡面 表示非  ()裡面分組匹配 有用的資訊
    url_regex = re.compile('<a[^>]+href=["\'](.*?)["\']',re.IGNORECASE)
    return url_regex.findall(html_content)

6.python 有一個現成的隨機生成UA(user-agent)的第三方庫:fake-useragent

class CrawlerCommon(Thread):
    """
    實現一個通用爬蟲,涵蓋基本的爬蟲功能及涉及一些反爬蟲技術
    """
    def __init__(self,init_url):
        # super(CrawlerCommon,self).__init__()
        super().__init__()
        __ua = UserAgent() #隨機User-Agent
        self.seed_url = init_url #初始化爬取種子網址
        self.crawler_queue = queue.Queue() # 先進先出 使用不同的佇列會造成BFS和DFS的效果
        self.crawler_queue.put(init_url) #將種子網址放入佇列
        self.visited = {init_url:0} #初始化爬蟲深度為0
        self.rp = get_robots(init_url) #初始化robots解析器
        self.headers = {'User-Agent':__ua.random} #生成一個隨機user-agent
        self.link_regex = '/(lady|fashion)' # 抽取網址的過濾條件
        self.throttle = Throttle(5.0) #下載限流器間隔為5秒
        self.mcache = Mongo_cache.MongoCache() #初始化mongo-cache
        self.time_sleep = 3

7.使用裝飾器進行重試下載類

@retry(stop_max_attempt_number=3)
    def retry_download(self,url_str,data,method,proxies):
        if method == "POST":
            result = requests.get(url_str,data=data,headers=self.headers,proxies=proxies)
        else:
            result = requests.get(url_str,headers = self.headers,timeout=3,proxies=proxies)
        assert result.status_code == 200 #此處為斷言,判斷狀態碼是否為200
        return result.content

8.真正下載類

    def download(self,url_str,data=None,method='GET',proxies={}):
        print("download url is ::::::",url_str)
        try:
            result = self.retry_download(url_str,data,method,proxies)
        except Exception as e:
            print("異常",e)
            result = None
        return result

9.補全下載連結

    def nomalize(self,url_str):
        real_url,_ = urldefrag(url_str)
        return urljoin(self.seed_url,real_url)
  1. 將結果存入資料庫,存入前檢查內容是否存在
    def save_result(self,html_content,url_str):
        if url_str not in self.mcache:
            self.mcache[url_str] = html_content
        else:
            data_from_mongo = self.mcache[url_str]
            #初始化md5演算法
            md5_func_mongo = hashlib.md5()
            md5_func_download = hashlib.md5()
            #生成資料庫記錄的md5摘要
            md5_func_mongo.update(data_from_mongo)
            mongo_md5_str = md5_func_mongo.hexdigest()
            #生成下載資料的md5摘要
            md5_func_download.update(html_content)
            download_md5_str = md5_func_download.hexdigest()
            #對比下載結果是否和資料庫一樣
            if download_md5_str != mongo_md5_str:
                self.mcache[url_str] = html_content

11.進行爬取的主要方法

    def run(self):
        while not self.crawler_queue.empty():
            url_str = self.crawler_queue.get()
            # print('url_str is:::::',url_str)
            #檢測robots.txt檔案規則
            if self.rp.can_fetch(self.headers["User-Agent"],url_str):
                self.throttle.wait_url(url_str)
                random_oper = randint(0,5)
                if random_oper ==1:
                    time.sleep(self.time_sleep + randint(0,2))
                else:
                    time.sleep(self.time_sleep - randint(0,2))

                depth = self.visited[url_str]
                if depth < MAX_DEP:
                    #下載連結
                    html_content = self.download(url_str)
                    #儲存連結
                    if html_content is not None:
                        self.mcache[url_str]=html_content
                        save_url(html_content,url_str)
                    #篩選出頁面所有連結
                    url_list = extractor_url_lists(html_content.decode('gb18030'))
                    #篩選需要爬取的連結
                    filter_urls = [link for link in url_list if re.search(self.link_regex,link)]
                    for url in filter_urls:
                        #補全連結
                        real_url = self.nomalize(url)
                        #判斷連結是否訪問過
                        if real_url not in self.visited:
                            self.visited[real_url] = depth + 1
                            self.crawler_queue.put(real_url)
            else:
                print("robots.txt 禁止下載:",url_str)

12.下載限流器

class Throttle(object):

    def __init__(self,delay):
        self.domains = {} #可以放在資料庫中
        self.delay = delay #兩次間隔下載間隔

    def wait_url(self,url_str):
        #以netloc為基礎進行休眠
        domain_url = urlparse(url_str).netloc #取出網址域名(netloc)
        last_accessed = self.domains.get(domain_url) #取出域名的上次下載時間
        if self.delay > 0 and last_accessed is not None:
            #將當前時間和上次下載時間相減,得出兩次下載時間間隔,然後用休眠時間(delay)減去這個時間間隔
            #如果大於0就休眠,否則直接下載後續連結
            sleep_interval = self.delay-(datetime.now()-last_accessed).seconds
            if sleep_interval > 0:
                time.sleep(sleep_interval)


        self.domains[domain_url] = datetime.now() #當前時間以域名作為key存到domains字典中

13.隨機代理

class RandomProxy(object):
    """
    隨機代理
    """
    def __init__(self):
        self.proxies = []
        self.headers = {
            "User-Agent":"wsf"
        }
    def crawl_proxies(self):
        """
        抓取生成代理
        :return:
        """
        self.proxies.append('192.168.1.1')
        self.proxies.append('192.168.1.2')

    def verify_proxies(self):
        """
        校驗每一個代理是否可用
        :return:
        """
        invalid_ip = []
        for ip_str in self.proxies:
            proxies = {"http":ip_str}
            r = requests.get("http://www.baidu.com",proxies=proxies,headers =self.headers)
            if r.status_code ==200:
                continue
            else:
                invalid_ip.append(ip_str)
        for remove_ip in invalid_ip:
            self.proxies.remove(remove_ip)

    def get_one_proxy(self):
        return random.choice(self.proxies)

if __name__ == "__main__":
    crawler = CrawlerCommon("http://fashion.163.com/")
    crawler.run()