初探Scrapy爬虫框架之百度网页爬取

scrapy框架及百度网页爬取与保存

大家好,最近本科即将毕业,即将迎来研究生生涯,可能自己的研究方向和大数据人工智能有关,那自然免不了网络爬虫的学习。之前接触过request库用于网络爬虫,但为了后续框架式的系统开发,我尝试开始使用Scrapy框架,希望记录一下自己的学习过程,和大家一起交流分享。

一、scrapy框架简介

太多的专业性解释我这里就不提了,我也是刚接触scrapy框架,我也不想长篇大作的从网上复制粘贴,但我可以从我自己这段时间的学习过程,给你们推荐一下比较适合新手看的一篇关于scrapy的学习博客,可以帮助我们从整体对scrapy框架有一个认识。

  1. 爬虫框架Scrapy个人总结(详细)熟悉

二、自己初使用的心得体会

可能有错,欢迎批评指正:

1、爬虫之前明确目标

整体考虑的问题有两点:
1、我们要从哪里爬 :例如本次我测试就是从百度搜索引擎爬,再比如你的爬取目标可能是小说网站,也可能是电影网站,音乐网站,淘宝等
2、我们要爬哪些内容:例如本次我测试就是简单地根据搜索百度关键字爬取相应数量的网页,再比如你的目标是电影网站,那你可能想爬电影名称及对应的url链接等
总之,我们不可急于求成,一定要明白此次爬虫的目标及内容,后续工作才更好展开。

2、scrapy框架的简单使用流程

1、编写items.py:第一步我们需要编写items.py,即存放我们要保存的的数据的容器。这里就体现出了我们刚刚第一步明确爬取内容的优势了,例如我们可以在这里定义电影标题,电影url等变量,当然,我们后续可以继续在这添加可能开始遗漏的变量。

import scrapy
class SpiderItems(scrapy.Item): #创建一个类,继承scrapy.item类
title = scrapy.Field() # 根据自己要爬取的内容进行创建
url = scrapy.Field()
...

2、编写spider.py:第二步我们需要编写自己的爬虫程序,这也是我们爬虫最关键的一部分,用于爬取我们想要内容的具体方式细节。初始化:name变量为该爬虫py文件名,allowed_domains变量为限定我们爬取的域名(不用加http开头),start_urls变量为初始化爬取的url链接。

class Spider(scrapy.Spider): # 继承Spider类
    name = "spider" # 爬虫的唯一标识,不能重复,启动爬虫的时候要用
    allowed_domains = ['www.baidu.com'] # 限定域名,只爬取该域名下的网页
    start_urls = ["http://www.baidu.com/" ] # 开始爬取的链接  

parse函数其实就是初始的回调函数,即程序会将初始化的url链接发起request请求,随后程序会将响应结果作为参数传递给parse函数的response,我们在parse函数中就可以对response进行处理,解析出的item内容信息可以通过yield item传递给 pipelines.py做进一步数据处理,解析出的url则加入爬取队列,负责进一步处理。
3、编写pipelines.py:第三步我们需要编写自己的数据处理程序,即对我们在spider.py中提取到的item数据进行处理与保存
4、编写middlewares.py(非必须):第四步是非必须的一步,是根据自己的需要去实现,其中包括两大部分:Downloader Middlewares:下载中间件,是处于Scrapy的Request和Requesponse之间的处理模块;Spider Middlewares:spider中间件,位于引擎和spider之间的框架,主要处理spider输入的响应和输出的结果及新的请求。
5、在settings.py中进行相关的设置:第五步是主要对全局的一些爬虫细节进行自定义的设置,详细可参考scrapy中的setting详解

3、scrapy框架的整体运行过程

我们不难从网上发现什么关于scrapy框架的介绍及初步使用教程,但从我自己刚刚学习起来,总有一种感觉就是似懂非懂,就是你通过查阅相关资料你可能很快掌握scrapy框架各部分大致要干什么,但你就是很难从整体掌握它们之间的联系,下面简要阐述作为初学者对于scrapy框架整体运行过程的理解:

在这里插入图片描述
具体解释如下:
Spiders,可以视为我们的spider.py,可见;Item Pipeline可以视为我们处理item的pipelines.py,可见;Scrapy Engine为引擎,处理整个系统的数据流处理,不可见;Scheduler为调度器,接受引擎发过来的请求,并将其加入队列中,在引擎再次请求时将请求提供给引擎,不可见;Downloader:下载器,下载网页内容,并将响应返回给spider,不可见;Downloader Middlewares:下载中间件,是处于Scrapy的Request和Requesponse之间的处理模块,在middlewares.py中可以自定义编写,可见;Spider Middlewares:spider中间件,位于引擎和spider之间的框架,主要处理spider输入的响应和输出的结果及新的请求,在middlewares.py中可以自定义编写,可见。
详细流程参考如下:

1.spider的yeild将request发送给engine
2.engine对request不做任何处理发送给scheduler
3.scheduler,生成request交给engine
4.engine拿到request,通过middleware发送给downloader
5.downloader在\获取到response之后,又经过middleware发送给engine
6.engine获取到response之后,返回给spider,spider的parse()方法对获取到的response进行处理,解析出items或者requests
7.将解析出来的items或者requests发送给engine
8.engine获取到items或者requests,将items发送给ItemPipeline,将requests发送给scheduler(注意:只有调度器中不存在request时,程序才停止,及时请求失败scrapy也会重新进行请求)

三、百度网页爬取及保存流程

1、初始url的构造

我们用浏览器打开百度搜索页面,输入关键字,这里我们输入“python”,可以看见url为https://www.baidu.com/baidu?wd=python&tn=monline_3_dg&ie=utf-8
在这里插入图片描述
不难看出wd参数代表我们的关键字信息,其余参数我们可以忽略,由此我们可以构造初始的url的代码为:‘https://www.baidu.com/baidu?wd={}’.format(keyword).

2、网页标题及url链接的提取

在百度搜索结果页面按F12,即打开了开发者工具,我们现在假设要定位到本页的第一个网页的标题,则可以按以下操作进行:
在这里插入图片描述
即可以直接定位到标题所在的部分,结果如下:
在这里插入图片描述
我们观察分析该标题所在html的位置(我们这里没有采取正则表达式的方式,因为对初学者可能难度较大,而是采用XPath的方式,具体参考这个链接,我个人感觉这种提取html内容的方式很适合初学者),可以写成如下的表达式://div[@id=“wrapper_wrapper”]/div[@id=“container”]/div[@id=
“content_left”]//h3/a/em/text(),同理,每个网页的真实url也可以此种形式进行提取,表达式为:
//div[@id=“wrapper_wrapper”]/div[@id=“container”]/div[@id=“content_left”]//h3/a/@href。

3、百度翻页操作

翻页,其实就是我们爬取完了一页的内容信息,想继续往下一页进行爬取,此时我们需要找到“下一页”按钮对应的跳转链接,方法一样,就是先选中“下一页”,找到在html的位置,再用XPath提取该链接,表达式为://div[@id=“page”]/a[contains(text(),“下一页”)]/@href,提取到后就可以作为新的url发起请求,获取下一页的内容再循环进行爬取。
在这里插入图片描述

4、应对百度的反爬虫机制策略

参考链接
我刚开始尝试爬取百度网页时,没有添加任何的应对反爬虫的机制,发现一个信息都爬不下来,简直就是还没开始就结束了,后来查阅资料发现百度这些年做了很多反爬虫机制,所以如果想像爬取其它小网站的形式去爬百度,基本不可能了,我主要采用了两种机制:随机user-agent和随机请求延时,具体怎么操作,直接参考我贴的代码(middlewares.py)如下:

from scrapy import signals
import random
import time
class SpiderSpiderMiddleware: # 默认生成的,没有使用到,可以忽略
    # Not all methods need to be defined. If a method is not defined,
    # scrapy acts as if the spider middleware does not modify the
    # passed objects.

    @classmethod
    def from_crawler(cls, crawler):
        # This method is used by Scrapy to create your spiders.
        s = cls()
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s

    def process_spider_input(self, response, spider):
        # Called for each response that goes through the spider
        # middleware and into the spider.

        # Should return None or raise an exception.
        return None

    def process_spider_output(self, response, result, spider):
        # Called with the results returned from the Spider, after
        # it has processed the response.

        # Must return an iterable of Request, dict or Item objects.
        for i in result:
            yield i

    def process_spider_exception(self, response, exception, spider):
        # Called when a spider or process_spider_input() method
        # (from other spider middleware) raises an exception.

        # Should return either None or an iterable of Request, dict
        # or Item objects.
        pass

    def process_start_requests(self, start_requests, spider):
        # Called with the start requests of the spider, and works
        # similarly to the process_spider_output() method, except
        # that it doesn’t have a response associated.

        # Must return only requests (not items).
        for r in start_requests:
            yield r

    def spider_opened(self, spider):
        spider.logger.info('Spider opened: %s' % spider.name)
class MyproxiesSpiderMiddleware(object): # 本来尝试随机ip的,后来网上的免费ip有问题就没用到,也可以暂时忽略

      def __init__(self,ip):
          self.ip=ip
      @classmethod
      def from_crawler(cls, crawler):
          return cls(
              ip=crawler.settings.get('IPPOOL')
          )
      def process_request(self, request, spider):
          thisip=random.choice(self.ip)
          print("this is ip:"+thisip["ipaddr"])
          request.meta["proxy"]="http://"+thisip["ipaddr"]

class RandomDelayMiddleware(object):
    '''
    设置每次请求的随机延时
    '''
    def __init__(self, delay):
        self.delay = delay

    @classmethod
    def from_crawler(cls, crawler):
        delay = crawler.spider.settings.get("RANDOM_DELAY", 10) # 通过from_crawler机制可从settings.py中获取变量值
        return cls(delay)

    def process_request(self, request, spider):
        delay = random.randint(5, self.delay) #5到self.delay中随机延时,delay在settings中设置的是15
        print("此次延时为:"+str(delay))
        time.sleep(delay)

class SpiderDownloaderMiddleware:
    '''
    设置每次请求的随机User-Agent
    '''
    def __init__(self, user_agent):
        self.user_agent = user_agent

    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            user_agent=crawler.settings.get('MY_USER_AGENT') # 从settings.py中获取MY_USER_AGENT变量值
        )

    def process_request(self, request, spider):
        agent = random.choice(self.user_agent)
        print("this is agent:"+agent)
        request.headers['User-Agent'] = agent

5、保存网页

需要有urllib库,代码不长,直接将代码贴出来你们理解一下(pipelines.py):

import urllib.request
def getHtml(url):
    html = urllib.request.urlopen(url,timeout=10).read()
    return html
#通过设置timeout参数用于解决ConnectionResetError问题
def saveHtml(file_name,file_content):
    with open(file_name.replace('/','_')+'.html','wb') as f:
        f.write(file_content)
class SpiderPipeline:
    def process_item(self, item, spider):
        #print('baiduPipeline',item)
        #item是从baidu.py解析后传过来的
        name = ''.join(item['title'])
        html = getHtml(item['visit_url'][0]) # 根据url请求并获取响应内容
        saveHtml(name,html) # 将响应的内容保存为html格式
        print("该网页爬取结束")
        return item

四、其余相关完整代码

1、items.py

import scrapy

class baiduItems(scrapy.Item):
    title = scrapy.Field()#标题
    visit_url = scrapy.Field()#连接

2、baidu.py

import scrapy
import sys
from spider.items import baiduItems
class baiduSearchSpider(scrapy.Spider):
    name = 'baidu' # 必须与你该py文件名一致
    allowed_domains = ['www.baidu.com']
    keyword = input("请输入搜索关键字:")
    page_num = int(input("请输入所需爬取网页数量:"))
    start_urls = ['https://www.baidu.com/baidu?wd={}'.format(keyword)]
    base_site = 'https://www.baidu.com'
    num = 0 # 用于表示以爬取的网页数目
    page = 1 # 用于显示当前所在页码
    print("您当前所在页码::"+str(page))
    def parse(self, response):
        # 在同一页码循环爬取每个网页的信息
        for a in response.xpath('//div[@id="wrapper_wrapper"]/div[@id="container"]/div[@id="content_left"]//h3/a'):
            self.num +=1
            print("正在爬取第{}个网页:".format(self.num))
            # 判断self.num是否超过self.page_num
            if self.num <= self.page_num:
                item = baiduItems()
                #title = ''.join(a.xpath('./em/text() | ./text()').extract()) # 提取标题
                #item['title'] = title.strip() # 加载标题给item
                item['title'] = str(self.num) # 我这里使用序号当做网页标题,也可使用上面两行代码
                url =  a.xpath('@href').extract()  # 提取链接
                item['visit_url'] = url # 加载链接给item
                yield item
            else:
                sys.exit(0) # 结束运行
        #获取下一页
        next_page_url = self.base_site+response.xpath('//div[@id="page"]/a[contains(text(),"下一页")]/@href').extract_first()
        print("下一页的链接为:"+ next_page_url)
        yield scrapy.Request(next_page_url, callback=self.parse) # 使用"下一页"的url去请求,使用parse函数作为回调函数接收response
        self.page+=1
        print("您当前所在页码::"+str(self.page))

3、settings.py

# -*- coding: utf-8 -*-

# Scrapy settings for spider project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
#     https://docs.scrapy.org/en/latest/topics/settings.html
#     https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
#     https://docs.scrapy.org/en/latest/topics/spider-middleware.html
BOT_NAME = 'spider'
SPIDER_MODULES = ['spider.spiders']
NEWSPIDER_MODULE = 'spider.spiders' # 相关路径名

# Obey robots.txt rules
ROBOTSTXT_OBEY = False # 将该参数设置为False,即表示本次爬虫不遵守robots.txt rules
ITEM_PIPELINES = {
   'spider.pipelines.SpiderPipeline': 1, # 启用我们在pipelines中自定义的SpiderPipeline类
}
DOWNLOADER_MIDDLEWARES = {
    'spider.middlewares.RandomDelayMiddleware':543, # 启用我们在中间件自定义的随机延时类
    'scrapy.downloadermiddleware.useragent.UserAgentMiddleware': None, # 关闭scrapy默认的设置useragent的方法
    'spider.middlewares.SpiderDownloaderMiddleware': 543, # 启用我们在中间件自定义的随机useragent类
   # 'spider.middlewares.MyproxiesSpiderMiddleware': 543,
}

RANDOM_DELAY = 15 # 最大延时时间,我设置为15'''IPPOOL=[# 有问题的ip池
    {"ipaddr":"61.129.70.131:8080"},
    {"ipaddr":"61.152.81.193:9100"},
    {"ipaddr":"120.204.85.29:3128"},
    {"ipaddr":"219.228.126.86:8123"},
    {"ipaddr":"61.152.81.193:9100"},
    {"ipaddr":"218.82.33.225:53853"},
    {"ipaddr":"223.167.190.17:42789"}
]
'''
# 应该正确的一些usera-agent信息
MY_USER_AGENT = [
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)",
    "Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
    "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)",
    "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
    "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
    "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
    "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
    "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5",
    "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20",
    "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.11 TaoBrowser/2.0 Safari/536.11",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71 Safari/537.1 LBBROWSER",
    "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; LBBROWSER)",
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E; LBBROWSER)",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.84 Safari/535.11 LBBROWSER",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)",
    "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; QQBrowser/7.0.3698.400)",
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SV1; QQDownload 732; .NET4.0C; .NET4.0E; 360SE)",
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)",
    "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1",
    "Mozilla/5.0 (iPad; U; CPU OS 4_2_1 like Mac OS X; zh-cn) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5",
    "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b13pre) Gecko/20110307 Firefox/4.0b13pre",
    "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:16.0) Gecko/20100101 Firefox/16.0",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11",
    "Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
    ]

4、起始运行代码(run.py)

from scrapy import cmdline
cmdline.execute("scrapy crawl baidu".split()) # 注意crawl后面跟着的是你的爬虫py文件名,且不用加后缀.py

5、本人项目目录参考截图

在这里插入图片描述

五、总结

其实这次是本人的开通csdn博客的第一天,写的第一篇博客,介绍的内容也是本人最近刚学的关于scrapy框架和百度网页爬取的知识,可能其中我说错了不少地方,希望大家可以理解一下,毕竟在此方向我也是个初学者,但都是自己的亲身经历和感受,也是希望大家可以一起多交流指正,共同进步。

  • 9
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值