来源:麦叔编程

作者:Kevin。

如果你真正写过爬虫,你一定遇到过这些问题:

    爬取数据的时候IP被封或者被限制网页数据库时时刻刻都在更新,不可能每次爬取都爬整站,需要做增量爬取数据量巨大,即使用了scrapy等多线程框架也是杯水车薪
要解决这三种场景,都需要使用某种数据库,而redis是其中最合适的一种。

本文通过几个案例,学习用redis数据库解决以上问题:

    使用基于redis的代理池,防止被封号使用redis管理爬取状态,实现增量式爬虫使用redis做分布式爬虫实现巨量数据爬取,著名的分布式爬虫方案scapy-redis也是类似原理



redis可以存储爬取的数据

当爬虫工程师想构建一个ip代理池的时候,redis绝对是首选。

下面我们来看一段代码:

import redisimport requestsfrom lxml import etreeconn = redis.Redis(host='127.0.0.1', port=6379)headers = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36 Edg/87.0.664.47"}def get_https_proxy(num):     https_proxy_url = f"http://www.nimadaili.com/https/{num}/"    resp = requests.get(url=https_proxy_url, headers=headers).text    # 使用xpath提取代理ip的字段    tree = etree.HTML(resp)    https_ip_list = tree.xpath('/html/body/div/div[1]/div/table//tr/td[1]/text()')    # 将爬下来的代理ip以列表(键名为'https')元素的形式推入数据库    [conn.lpush('https',ip) for ip in https_ip_list]    print('Redis数据库有HTTPS代理IP数量为:',conn.llen('https'))# 获取代理网站1——6页的代理ipfor n in range(1,6):    get_https_proxy(n)
redis命令交互端输入:

lrange https 0 -1
就可以看到爬到的代理ip:




取出代理ip也非常简单:

import redisconn = redis.Redis(host='127.0.0.1', port=6379)proxies_ip = conn.rpop('https').decode('utf-8')print(proxies_ip)>> 106.14.247.221:8080
是不是非常简单,学会这招,再也不怕被封IP了。

redis可以辅助实现增量爬虫

当爬虫工程师需要写增量式爬虫的时候,一定会考虑使用redis的set数据类型进行url“去重”,为什么呢?

现在假如有一个需求:

1. 爬取菜市场的历史菜价。

2. 需要每天更新当日的价格。

那么我的爬取思路就是:

1. 爬取每日菜价详情页的url,以set类型存入redis数据库。

2. 爬取redis数据库中所有的url对应菜价数据。

3. 然后第二天或者(第N天),再次爬取每日的菜价详情页的url,以set类型存入redis数据库。如果在存入数据库的时候返回0,则表示数据库中已存在相同的url,则不需要爬取该详情页,如果返回1,则表示数据库中未存在该url,则需要爬取该详情页。




上面看得有点绕,我们来实际操作下:

127.0.0.1:6379> sadd url wwww.baidu.com(integer) 1127.0.0.1:6379> sadd url wwww.baidu.com(integer) 0
www.baidu.com作为url的成员第一次存入数据库的时候,返回的是(integer) 1;然后我们第二次进行相同的操作时,数据库返回的是(integer) 0,这表示url成员中已经存在了该值。

利用redis这个特性,我们可以很便利地做到url去重的功能。

# 这是我写的某个爬虫项目的运行爬虫的实例方法def run_spider(self):  # 遍历爬取所有详情页的url  for url in self.get_link_list():    # 将url以set类型存入redis数据库  j = self.conn.sadd('url',url)    # 判断返回值是1或0  if j == 1:      # 如果返回值为1,爬取该详情页数据,反之跳过      self.parse_detail(url)  # 数据持久化存储  self.work_book.save('./price.xls')redis可以用作分布式爬虫的调度器

鼎鼎大名的分布式爬虫框架:scrapy-redis一定有听说过吧?




分布式爬虫一听感觉很高大上,其实原理很简单,就是把scrapy的调度器共享到服务器上去,然后各个设备的爬虫从服务器上获取需要爬取数据的url。

我们来温习下scrapy五大组件之一的调度器的作用:

调度器(Scheduler):用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想象成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址。

下面我们分三步模拟部署一个简单的分布式爬虫:

1. 第一步,爬取需要解析数据的url;
import redisimport requestsfrom lxml import etreeconn = redis.Redis(host='127.0.0.1', port=6379)headers = {    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',}# 获取一部小说所有的章节的url,并以list数据形式存入redis。def get_catalogue():    response = requests.get('https://www.tsxs.org/16/16814/', headers=headers)    tree = etree.HTML(response.text)    catalog_url_list = tree.xpath('//*[@id="chapterlist"]/li/a/@href')    return catalog_url_list    for i in get_catalogue():    full_link = 'https://www.tsxs.org' + i    conn.lpush('catalogue',full_link)
这段代码实现了向redis注入所有需要爬取小说页面的url的功能。

127.0.0.1:6379> lrange catalogue 0 -1  1) "https://www.tsxs.org/16/16814/13348772.html"  2) "https://www.tsxs.org/16/16814/13348771.html"  3) "https://www.tsxs.org/16/16814/13348770.html"  4) "https://www.tsxs.org/16/16814/13348769.html"  5) "https://www.tsxs.org/16/16814/13348768.html"  6) "https://www.tsxs.org/16/16814/13348767.html"  7) "https://www.tsxs.org/16/16814/13348766.html"  8) "https://www.tsxs.org/16/16814/13348765.html"  9) "https://www.tsxs.org/16/16814/13348764.html" 10) "https://www.tsxs.org/16/16814/13348763.html" 11) "https://www.tsxs.org/16/16814/13348762.html" 12) "https://www.tsxs.org/16/16814/13348761.html" 13) "https://www.tsxs.org/16/16814/13348760.html" 14) "https://www.tsxs.org/16/16814/13348759.html" 15) "https://www.tsxs.org/16/16814/13348758.html" 16) "https://www.tsxs.org/16/16814/13348757.html" 17) "https://www.tsxs.org/16/16814/13348756.html" 18) "https://www.tsxs.org/16/16814/13348755.html" 19) "https://www.tsxs.org/16/16814/13348754.html" 20) "https://www.tsxs.org/16/16814/13348753.html" 21) "https://www.tsxs.org/16/16814/13348752.html" 22) "https://www.tsxs.org/16/16814/13348751.html" 23) "https://www.tsxs.org/16/16814/13348750.html" 24) "https://www.tsxs.org/16/16814/13348749.html" 25) "https://www.tsxs.org/16/16814/13348748.html" 26) "https://www.tsxs.org/16/16814/13348747.html" 27) "https://www.tsxs.org/16/16814/13348746.html" 28) "https://www.tsxs.org/16/16814/13348745.html" 29) "https://www.tsxs.org/16/16814/13348744.html" 30) "https://www.tsxs.org/16/16814/13348743.html" 31) "https://www.tsxs.org/16/16814/13348742.html" 32) "https://www.tsxs.org/16/16814/13348741.html" 33) "https://www.tsxs.org/16/16814/13348740.html" 34) "https://www.tsxs.org/16/16814/13348739.html" 35) "https://www.tsxs.org/16/16814/13348738.html" 36) "https://www.tsxs.org/16/16814/13348737.html" 37) "https://www.tsxs.org/16/16814/13348736.html" 38) "https://www.tsxs.org/16/16814/13348735.html" 39) "https://www.tsxs.org/16/16814/13348734.html" 40) "https://www.tsxs.org/16/16814/13348733.html" 41) "https://www.tsxs.org/16/16814/13348732.html" 42) "https://www.tsxs.org/16/16814/13348731.html" 43) "https://www.tsxs.org/16/16814/13348730.html" 44) "https://www.tsxs.org/16/16814/13348729.html" 45) "https://www.tsxs.org/16/16814/13348728.html" 46) "https://www.tsxs.org/16/16814/13348727.html" 47) "https://www.tsxs.org/16/16814/13348726.html" 48) "https://www.tsxs.org/16/16814/13348725.html" 49) "https://www.tsxs.org/16/16814/13348724.html" 50) "https://www.tsxs.org/16/16814/13348723.html" 51) "https://www.tsxs.org/16/16814/13348722.html" 52) "https://www.tsxs.org/16/16814/13348721.html" 53) "https://www.tsxs.org/16/16814/13348720.html" 54) "https://www.tsxs.org/16/16814/13348719.html" 55) "https://www.tsxs.org/16/16814/13348718.html" 56) "https://www.tsxs.org/16/16814/13348717.html" 57) "https://www.tsxs.org/16/16814/13348716.html" 58) "https://www.tsxs.org/16/16814/13348715.html" 59) "https://www.tsxs.org/16/16814/13348714.html" 60) "https://www.tsxs.org/16/16814/13348713.html" 61) "https://www.tsxs.org/16/16814/13348712.html" 62) "https://www.tsxs.org/16/16814/13348711.html" 63) "https://www.tsxs.org/16/16814/13348710.html" 64) "https://www.tsxs.org/16/16814/13348709.html" 65) "https://www.tsxs.org/16/16814/13348708.html" 66) "https://www.tsxs.org/16/16814/13348707.html" 67) "https://www.tsxs.org/16/16814/13348706.html" 68) "https://www.tsxs.org/16/16814/13348705.html" 69) "https://www.tsxs.org/16/16814/13348704.html" 70) "https://www.tsxs.org/16/16814/13348703.html" 71) "https://www.tsxs.org/16/16814/13348702.html" 72) "https://www.tsxs.org/16/16814/13348701.html" 73) "https://www.tsxs.org/16/16814/13348700.html" 74) "https://www.tsxs.org/16/16814/13348699.html" 75) "https://www.tsxs.org/16/16814/13348698.html" 76) "https://www.tsxs.org/16/16814/13348697.html" 77) "https://www.tsxs.org/16/16814/13348696.html" 78) "https://www.tsxs.org/16/16814/13348695.html" 79) "https://www.tsxs.org/16/16814/13348694.html" 80) "https://www.tsxs.org/16/16814/13348693.html" 81) "https://www.tsxs.org/16/16814/13348692.html" 82) "https://www.tsxs.org/16/16814/13348691.html" 83) "https://www.tsxs.org/16/16814/13348690.html" 84) "https://www.tsxs.org/16/16814/13348689.html" 85) "https://www.tsxs.org/16/16814/13348688.html" 86) "https://www.tsxs.org/16/16814/13348687.html" 87) "https://www.tsxs.org/16/16814/13348686.html" 88) "https://www.tsxs.org/16/16814/13348685.html" 89) "https://www.tsxs.org/16/16814/13348684.html" 90) "https://www.tsxs.org/16/16814/13348683.html" 91) "https://www.tsxs.org/16/16814/13348682.html" 92) "https://www.tsxs.org/16/16814/13348681.html" 93) "https://www.tsxs.org/16/16814/13348680.html" 94) "https://www.tsxs.org/16/16814/13348679.html" 95) "https://www.tsxs.org/16/16814/13348678.html" 96) "https://www.tsxs.org/16/16814/13348677.html" 97) "https://www.tsxs.org/16/16814/13348676.html" 98) "https://www.tsxs.org/16/16814/13348675.html" 99) "https://www.tsxs.org/16/16814/13348674.html"100) "https://www.tsxs.org/16/16814/13348673.html"101) "https://www.tsxs.org/16/16814/13348672.html"102) "https://www.tsxs.org/16/16814/13348671.html"103) "https://www.tsxs.org/16/16814/13348670.html"104) "https://www.tsxs.org/16/16814/13348669.html"105) "https://www.tsxs.org/16/16814/13348668.html"2. 第二步,配置redis的配置文件
    linux或者mac:redis.confwindows:redis.windows.conf代开配置文件修改:
      将bind 127.0.0.1进行删除关闭保护模式:protected-mode yes改为no
    结合着配置文件开启redis服务
      redis-server 配置文件
    启动客户端:
      redis-cli




将bind 127.0.0.1进行删除




protected-mode yes改为no

3. 第三步,使用局域网内别的设备访问redis服务;




开始爬取,

使用redis的lpop命令,执行后从数据库返回url,数据库中则清除该url。

import redisimport requestsfrom lxml import etreeconn = redis.Redis(host='192.168.125.101', port=6379)headers = {    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',}def extract_data(per_link):    response = requests.get(url=per_link, headers=headers).content    tree = etree.HTML(response.decode('gbk'))    title = tree.xpath('//*[@id="mains"]/div[1]/h1/text()')[0]    content = tree.xpath('//*[@id="book_text"]//text()')[0]    return title, contentdef save_to_pc(title, content):    print(title + "开始下载!")    with open(title+'.txt','w',encoding='utf-8')as f:        f.write(content)    print(title + "下载结束!")def run_spider():    print('开始运行爬虫!')    link = conn.lpop('catalogue').decode('utf-8')    title, content = extract_data(link)    save_to_pc(title, content)    print('下载结束!')run_spider()>> 开始运行爬虫!   章节目录 第105章 同样是君子开始下载!   章节目录 第105章 同样是君子下载结束!   下载结束!
就这样爬取一个url,redis中少一个url,直到被全部爬取完毕为止。
温馨提示:
1、在论坛里发表的文章仅代表作者本人的观点,与本网站立场无关。
2、论坛的所有内容都不保证其准确性,有效性,时间性。阅读本站内容因误导等因素而造成的损失本站不承担连带责任。
3、当政府机关依照法定程序要求披露信息时,论坛均得免责。
4、若因线路及非本站所能控制范围的故障导致暂停服务期间造成的一切不便与损失,论坛不负任何责任。
5、注册会员通过任何手段和方法针对论坛进行破坏,我们有权对其行为作出处理。并保留进一步追究其责任的权利。
回复

使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    • 售后服务
    • 关注我们
    • 社区新手

    QQ|手机版|小黑屋|数据通

    Powered by datatong.net X3.4  © 2008-2020 数据通