一、增量爬虫
增量爬虫
:就是使爬虫获取到的数据以增量的形式稳定增长。
增量爬虫的核心,就是去重。
(一)方案
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 = 'xxxxxxxxxx@qq.com'
# 授权码
password = 'xxxxxxxxxxxxxxxx'
# 收件人,可多个,用','隔开
receiver = 'xxxxxxxxxx@qq.com'
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()