一、增量爬蟲
增量爬蟲:就是使爬蟲獲取到的數據以增量的形式穩定增長。
增量爬蟲的核心,就是去重。
(一)方案
1.爬蟲結束
在保存到數據庫之前,查看數據是否重複,或者用update方法做更新操作。
2.爬蟲開始
在爬取數據前,查看這個url是否被爬過。
3.爬蟲中間
有時候在爬取一些網站的時候,可能得到了一些數據,但是這個網頁的更新速度比較快,這時候我們可以查看這個網頁是否有更新,如果更新了,響應的網站的數據就應該在數據庫更新。
實現步驟
將頁面的response.text用hash算法得到一個加密串,如果頁面更新,response.text內容肯定會發生變化,加密串就也會發生變化,所以可以通過判斷這個加密串是否重複,來判斷是否更新。
(二)去重方法
1.python中的set集合(×)
項目今天運行了,會在代碼中產生一個set,當程序關閉,set就失效了,明天如果運行,還是從頭開始,沒有去重的意義。
2.redis(√)
原因:
- redis有一個數據類型叫做set,這個set不允許重複
- redis是一個可以持久化的數據庫,可以將這些已經爬取的信息保存下來,下次運行就能找到
- redis是運行在內存上的,速度快,對程序影響小
做法
import hashlib,redis
def get_md5(value):
md5 = hashlib.md5()
md5.update(value.encode())
return md5.hexdigest()
# True表示重複
def before_request(url):
red = redis.Redis()
res = red.sadd('項目名:urlset',get_md5(url))
if res == 0:
return True
return False
項目–貓眼電影演員信息無限抓取(生產者消費者模式)
oop(增量爬蟲–判斷url是否被爬過)
import hashlib
from queue import Queue
import redis
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from lxml import etree
class Maoyan_actor():
def __init__(self,q):
self.q = q
self.driver = webdriver.PhantomJS()
self.wait = WebDriverWait(self.driver,20)
self.parse()
def get_md5(self,value):
md5 = hashlib.md5()
md5.update(value.encode('utf-8'))
return md5.hexdigest()
# true表示重複
def request_seen(self,url):
red = redis.Redis()
res = red.sadd('maoyan:actor_url', self.get_md5(url))
if res == 0:
return True
return False
def get_text(self,text):
if text:
return text[0]
return ''
def get_xpath_by_selenium(self,url,xpath):
self.driver.get(url)
webelement = self.wait.until(EC.presence_of_element_located((By.XPATH,xpath)))
webelement.click()
return etree.HTML(self.driver.page_source)
def parse(self):
while True:
if self.q.empty():
break
url = self.q.get()
#如果url所對應的演員已經爬取過了,跳過本次,繼續下載
if self.request_seen(url):
continue
html = self.get_xpath_by_selenium(url,'//div[@class="introduce"]/div[2]/a')
#解析內容
name = self.get_text(html.xpath('//div[@class="celeInfo-list"]/dl/dd[1]/text()'))
print(name)
#取出相關電影人,將其加入隊列。
url_list = html.xpath('//div[@class="item"]/div/a/@href')
# print(url_list)
for u in url_list:
self.q.put('https://maoyan.com'+u)
if __name__ == '__main__':
# base_url = 'https://maoyan.com/films/celebrity/29190'
#初始url列表
start_urls = [
'https://maoyan.com/films/celebrity/29515',#歐美
'https://maoyan.com/films/celebrity/29190',#中國
'https://maoyan.com/films/celebrity/31444',#日本
]
q = Queue()
#初始化隊列,將來所有的url都是從隊列裏面取的
for url in start_urls:
q.put(url)
Maoyan_actor(q)
多線程(增量爬蟲–判斷頁面是否更新)
import hashlib
from queue import Queue
import threading
import redis
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from lxml import etree
class Maoyan_actor(threading.Thread):
def __init__(self,name,q):
super().__init__()
self.q = q
self.name = name
self.driver = webdriver.PhantomJS()
self.wait = WebDriverWait(self.driver,20)
# self.parse()
def run(self):
self.parse()
def get_md5(self,value):
md5 = hashlib.md5()
md5.update(value.encode('utf-8'))
return md5.hexdigest()
def response_seen(self,content):
red = redis.Redis()
res = red.sadd('maoyan:actor_content', self.get_md5(content))
if res == 0:
return True
return False
# true表示重複
def request_seen(self,url):
red = redis.Redis()
res = red.sadd('maoyan:actor_url', self.get_md5(url))
if res == 0:
return True
return False
def get_text(self,text):
if text:
return text[0]
return ''
def get_xpath_by_selenium(self,url,xpath):
self.driver.get(url)
webelement = self.wait.until(EC.presence_of_element_located((By.XPATH,xpath)))
webelement.click()
html_str = self.driver.page_source
#查看頁面內容是否更新
if not self.response_seen(html_str):
return etree.HTML(html_str)
return None
def parse(self):
while True:
if self.q.empty():
break
url = self.q.get()
#如果url所對應的演員已經爬取過了,跳過本次,繼續下載
# if self.request_seen(url):
# continue
try:
html = self.get_xpath_by_selenium(url, '//div[@class="introduce"]/div[2]/a')
# 解析內容
name = self.get_text(html.xpath('//div[@class="celeInfo-list"]/dl/dd[1]/text()'))
print('{}==============================={}'.format(name, self.name))
print(name)
# 取出相關電影人,將其加入隊列。
url_list = html.xpath('//div[@class="item"]/div/a/@href')
# print(url_list)
for u in url_list:
self.q.put('https://maoyan.com' + u)
except Exception:
print('頁面爲空!')
if __name__ == '__main__':
# base_url = 'https://maoyan.com/films/celebrity/29190'
#初始url列表
start_urls = [
'https://maoyan.com/films/celebrity/29515',#歐美
'https://maoyan.com/films/celebrity/29190',#中國
'https://maoyan.com/films/celebrity/31444',#日本
]
q = Queue()
#初始化隊列,將來所有的url都是從隊列裏面取的
for url in start_urls:
q.put(url)
#創建列表
crawl_list = ['aa','bb','cc']
for crawl in crawl_list:
Maoyan_actor(crawl,q).start()
異常出現位置:一般異常都是在所在線程裏面出現,一旦出現,該線程如果沒有處理異常的代碼,該線程就會斷掉。
所以在工作中,一定要寫處理異常的代碼。
二、scrapy分佈式–筆趣閣
(一)什麼是分佈式
將一個任務分割成多份,每一份由一個計算機完成,最後所有的計算機能夠成爲一個整體,得到這個任務的結果。
分佈式數據
產生的原因:
原來一個數據庫都是放在一臺電腦上,但是由於用戶量的增多,造成數據庫壓力很大。所以產生了一個思想,就是用多臺電腦可以提供同樣的數據庫服務。
(二)scrapy分佈式
原來的項目都是部署在一臺電腦上的,這樣爬取速度雖然很快,但是我們還能提升,聯想到分佈式的思想,我們可以通過多臺電腦進行配合,使我們的爬取速度大幅度提升。
(三)scrapy和scrapy-redis的區別
- scrapy是一個通用爬蟲框架,但不支持分佈式
- scrapy-redis,在scrapy的基礎上增加一個redis組件,這個redis裏面設置了待爬取的url列表和存放每個url的hash值的指紋集合。通過這個做到了分佈式,使得多臺電腦可以聯合爬取一個任務。
(四)scrapy分佈式部署步驟
1.導包
from scrapy_redis.spiders import RedisSpider
更改spider繼承,讓spider繼承RedisSpider
2.初始url
註釋start_urls,新建一個py文件,將url存儲到redis數據中–設置任務
3.在spider增加類變量redis_key
4.在settings中進行配置
主機
# 配置scrapy-redis調度器
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 配置url去重
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 300
}
# 優先級隊列
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue'
# 主機名
REDIS_HOST = 'localhost'
# 端口號
REDIS_PORT = 6379
MONGO_URL = 'localhost'
MONGO_DATABASE = 'biquge'
從機
1.將star_urls初始化的代碼全部註釋
2.從機的redis可以關閉
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 去重
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 優先級隊列
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue'
# ITEM_PIPELINES = {
# 'scrapy_redis.pipelines.RedisPipeline': 300
# }
REDIS_HOST = '主機ip'
REDIS_PORT = 6379
MONGO_URL = '主機ip'
MONGO_DATABASE = 'biquge'
三、驗證碼
步驟
1.安裝tesseract
2.配置兩個環境變量
系統變量
用戶變量
3.驗證
4.安裝pytesseract
5.修改源代碼
6.使用
import pytesseract
from PIL import Image
image = Image.open('CheckCode.png')
# 把一個彩色圖變成灰度圖
image = image.convert('L')
tessdata_dir_config = '--tessdata-dir "D:\\Tesseract-OCR\\tessdata"'
# 去除干擾線
threshold = 128
table = []
for i in range(256):
if i < threshold:
table.append(0)
else:
table.append(1)
image = image.point(table,'1')
image_str = pytesseract.image_to_string(image,config=tessdata_dir_config)
print(image_str)
四、Fiddler
步驟
1.配置
2.下載證書
3.下載好後,打開並安裝
4.重啓fiddler
五、爬蟲的自定義監控
自定義監控:當我們寫好一個爬蟲,它可能以後也會運行。如果頁面發生變更,可以自動告訴我們這個爬蟲頁面內容獲取不到了,需要更改代碼。同時如果爬蟲出現故障,也可以告訴我們爬蟲出了什麼問題。
自定義監控的主要方法就是用郵件的形式讓爬蟲通知我們。
重點:如何發郵件
項目–筆趣閣
需求:
從biquge數據庫中,隨機選出一本小說,並將小說的內容整合在一起。用郵件的形式發送給我。
import smtplib
import pymongo,random
from email.mime.text import MIMEText
client = pymongo.MongoClient()
db = client['biquge']
def send_email(text):
# 發件人
msg_from = '[email protected]'
# 授權碼
password = 'xxxxxxxxxxxxxxxx'
# 收件人,可多個,用','隔開
receiver = '[email protected]'
subject = '每天一本小說'
msg = MIMEText(text,'plain','utf-8')
msg['Subject'] = subject
msg['From'] = msg_from
msg['To'] = receiver
try:
smtp = smtplib.SMTP()
# 連接郵件服務器
smtp.connect('smtp.qq.com')
# 登錄
smtp.login(msg_from,password)
# 發送
smtp.sendmail(msg_from,msg['To'].split(','),msg.as_string())
print('發送成功!')
except Exception:
print('發送失敗!')
def get_novel():
# 隨機找出一本書
title_list = []
db_titles = db['xuanhuan'].find({},{'_id':0,'title':1})
for title in db_titles:
if title['title'] in title_list:
continue
title_list.append(title['title'])
# 隨機選一本小說
title = random.choice(title_list)
# 通過小說名查找所有該書的數據
ones = db['xuanhuan'].find({'title':title})
text = ''
for one in ones:
for k,v in one['content'].items():
text += k + '\n' + v.replace('\r','\n')
send_email(text)
if __name__ == '__main__':
get_novel()