Python Scrapy 爬虫(二):scrapy 初试

接上篇,以前咱们搭建好了运行环境,至关于咱们搭好了炮台,如今就差猎物和武器了。html

1、选取猎物

此处选择爬取西刺代理 IP 做为示例项目,缘由有以下两点:python

  • 西刺代理数据规范,爬取简单,做为演示项目比较合适
  • 代理 IP 在咱们的爬虫中也许还能派上用场(虽然可用率低了点,但若是你不是走量的,平时本身用一下仍是不错的)

猎物 URLmysql

http://www.xicidaili.com/nn
复制代码

注:虽然西刺声称提供了全网惟一的免费代理 IP 接口,但彷佛并无什么用,由于根本不返回数据...咱们本身作点小工做仍是能够的。git

2、咱们的目标是

scrapy 初试计划实现的效果是:github

  • 从西刺代理网站上爬取免费的国内高匿代理 IP
  • 将爬取的代理 IP存入 MySQL 数据库中
  • 经过循环爬取的方式获取最新 IP,并经过设立数据库惟一键的方式进行简易版去重

3、目标分析

  正所谓知己知彼,至于胜多胜少,先不纠结。咱们先打开网站(使用 Chrome 打开),看见的大概是下面的这个东西。web

西刺代理

网页结构分析sql

  • 大体浏览咱们的目标网站,选取咱们须要的数据。从网页上咱们能够看到西刺代理国内高匿 IP 展现了国家、IP、端口、服务器地址、是否匿名、类型、速度、链接时间、存活时间、验证时间这些信息。
  • 在网页的数据展现区的字段名称(蓝色)区域点击右键 -> 检查,咱们发现该网页的数据是由
    进行渲染布局的
  • 把网页拖动到底部,发现网站的数据进行分布展现,咱们点击下一页翻页一观察就发现颇有规律的是页码参数就在 URL 的后面,当前第几页就传数字几,如:
http://www.xicidaili.com/nn/3
复制代码
  • 咱们看到国家是显示的国旗,速度与链接时间是显示的两个颜色块,彷佛不太好拿这三个信息?

  此时,咱们将鼠标移至网页中某一面小国旗的位置处点击右键 -> 检查,咱们发现这是一个 img 标签,其中 alt 属性有国家代码。明了了吧,这个国家信息咱们能拿到。chrome

  咱们再把鼠标移至速度的色块处点击右键 -> 检查,咱们能够发现有个 div 上有个 title 显示相似 0.876秒这种数据,而链接时间也是这个套路,因而基本肯定咱们的数据项都能拿到,且能经过翻页拿取更多 IP。数据库

4、准备武器弹药

4.1 准备武器

咱们的武器固然是 scrapywindows

4.2 准备弹药

弹药包括

  • pymsql (Python 操做 MySQL 的库)
  • fake-useragent (隐藏你的身份)
  • pywin32

5、开火

5.1 建立虚拟环境

C:\Users\jiang>workon

Pass a name to activate one of the following virtualenvs:
==============================================================================
test
C:\Users\jiang>mkvirtualenv proxy_ip
Using base prefix 'd:\\program files\\python36'
New python executable in D:\ProgramData\workspace\python\env\proxy_ip\Scripts\python.exe
Installing setuptools, pip, wheel...done.
复制代码

5.2 安装第三方库

注意:下文的命令中,只要命令前方有 "(proxy_ip)" 标识,即表示该命令是在上面建立的 proxy_ip 的 python 虚拟环境下执行的。

1 安装 scrapy

(proxy_ip) C:\Users\jiang>pip install scrapy -i https://pypi.douban.com/simple
复制代码

若是安装 scrapy 时出现以下错误:

error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": http://landinghub.visualstudio.com/visual-cpp-build-tools
复制代码

可以使用以下方式解决:

  • 手动下载 twisted 的 whl 包,下载地址以下
https://www.lfd.uci.edu/~gohlke/pythonlibs/
复制代码

打开上方的 URL,搜索 Twisted,下载最新的符合 python 版本与 windows 版本的 whl 文件,如

twisted

如上图所示,Twisted‑18.4.0‑cp36‑cp36m‑win_amd64.whl 表示匹配 Python 3.6 的 Win 64 文件。这是与我环境匹配的。所以,下载该文件,而后找到文件下载的位置,把其拖动到 CMD 命令行窗口进行安装,以下示例:

(proxy_ip) C:\Users\jiang>pip install D:\ProgramData\Download\Twisted-18.4.0-cp36-cp36m-win_amd64.whl
复制代码

离线安装 twisted

再执行以下命令安装 scrapy 便可成功安装

(proxy_ip) C:\Users\jiang>pip install scrapy -i https://pypi.douban.com/simple
复制代码

2 安装 pymysql

(proxy_ip) C:\Users\jiang>pip install pymysql -i https://pypi.douban.com/simple
复制代码

3 安装 fake-useragent

(proxy_ip) C:\Users\jiang>pip install fake-useragent -i https://pypi.douban.com/simple
复制代码

4 安装 pywin32

(proxy_ip) C:\Users\jiang>pip install pywin32 -i https://pypi.douban.com/simple
复制代码

5.3 建立 scrapy 项目

1 建立一个工做目录(可选)

咱们能够建立一个专门的目录用于存放 python 的项目文件,例如:

我在用户目录下建立了一个 python_projects,也能够建立任何名称的目录或者选用一个本身知道位置的目录。

(proxy_ip) C:\Users\jiang>mkdir python_projects
复制代码

2 建立 scrapy 项目

进入工做目录,执行命令建立一个 scrapy 的项目

(proxy_ip) C:\Users\jiang>cd python_projects

(proxy_ip) C:\Users\jiang\python_projects>scrapy startproject proxy_ip
New Scrapy project 'proxy_ip', using template directory 'd:\\programdata\\workspace\\python\\env\\proxy_ip\\lib\\site-packages\\scrapy\\templates\\project', created in:
    C:\Users\jiang\python_projects\proxy_ip

You can start your first spider with:
    cd proxy_ip
    scrapy genspider example example.com
复制代码

至此,已经完成了建立一个 scrapy 项目的工做。接下来,开始咱们的狩猎计划吧...

5.4 关键配置编码

1 打开项目

使用 PyCharm 打开咱们刚刚建立好的 scrapy 项目,点击 "Open in new window" 打开项目

打开项目

scrapy 项目初始结构

2 配置项目环境

File -> Settings -> Project: proxy_ip -> Project Interpreter -> 齿轮按钮 -> add ...

设置项目环境

选择 "Existing environment" -> "..." 按钮

选择虚拟环境位置

找到以前建立的 proxy_ip 的虚拟环境的 Scripts/python.exe,选中并肯定

选择 proxy_ip 虚拟环境的 python.exe

虚拟环境 proxy_ip 的位置默认位于 C:\Users\username\envs,此处个人虚拟机位置已经经过修改 WORK_ON 环境变量更改。

3 配置 items.py

items 中定义了咱们爬取的字段以及对各字段的处理,能够简单地相似理解为这是一个 Excel 的模板,咱们定义了模块的表头字段及字段的属性等等,而后咱们按照这个模块往表格里填数。

class ProxyIpItem(scrapy.Item):
    country = scrapy.Field()
    ip = scrapy.Field()
    port = scrapy.Field( )
    server_location = scrapy.Field()
    is_anonymous = scrapy.Field()
    protocol_type = scrapy.Field()
    speed = scrapy.Field()
    connect_time = scrapy.Field()
    survival_time = scrapy.Field()
    validate_time = scrapy.Field()
    source = scrapy.Field()
    create_time = scrapy.Field()

    def get_insert_sql(self):
        insert_sql = """ insert into proxy_ip( country, ip, port, server_location, is_anonymous, protocol_type, speed, connect_time, survival_time, validate_time, source, create_time ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) """

        params = (
                    self["country"], self["ip"], self["port"], self["server_location"],
                    self["is_anonymous"], self["protocol_type"], self["speed"], self["speed"],
                    self["survival_time"], self["validate_time"], self["source"], self["create_time"]
                  )
        return insert_sql, params
复制代码

4 编写 pipelines.py

  pipeline 直译为管道,而在 scrapy 中的 pipelines 的功能与管道也很是类似,咱们能够在 piplelines.py 中定义多个管道。你能够这么来理解,好比:有一座水库,咱们要从这个水库来取水到不一样的地方,好比自来水厂、工厂、农田...

  因而咱们建了三条管道一、二、3,分别链接到自来水厂,工厂、农田。当管道创建好以后,只要咱们须要水的时候,打开开关(阀门),水库里的水就能源源不断的流向不一样的目的地。

  而此处的场景与上面的水库取水有不少类似性。好比,咱们要爬取的网站就至关于这个水库,而咱们在 pipelines 中创建的管道就至关于创建的取水管道,只不过 pipelines 中定义的管道不是用来取水,而是用来存取咱们爬取的数据,好比,你能够在 pipelines 中定义一个流向文件的管道,也能够创建一个流向数据库的管道(好比MySQL/Mongodb/ElasticSearch 等等),而这些管道的阀门就位于 settings.py 文件中,固然,你也能够多个阀门同时打开,你甚至还能够定义各管道的优先级。

  若是您能看到这儿,您是否会以为颇有意思。原来,那些程序界的大佬们,他们在设计这个程序框架的时候,其实参照了不少现实生活中的实例。在此,我虽然不敢确定编写出 scrapy 这样优秀框架的前辈们是否是参考的现实生活中的水库的例子在设计整个框架,但我能肯定的是他们必定参考了和水库模型相似的场景。

  在此,我也向这些前辈致敬,他们设计的框架很是优秀并且简单好用,感谢!

前面说了这么多,都是个人一些我的理解以及感触,下面给出 pipelines.py 中的示例代码:

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

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html

import pymysql
from twisted.enterprise import adbapi


class ProxyIpPipeline(object):
    """ xicidaili """
    def __init__(self, dbpool):
        self.dbpool = dbpool

    @classmethod
    def from_settings(cls, settings):
        dbparms = dict(
            host=settings["MYSQL_HOST"],
            db=settings["MYSQL_DBNAME"],
            user=settings["MYSQL_USER"],
            passwd=settings["MYSQL_PASSWORD"],
            charset='utf8',
            cursorclass=pymysql.cursors.DictCursor,
            use_unicode=True,
        )
        dbpool = adbapi.ConnectionPool("pymysql", **dbparms)
        # 实例化一个对象
        return cls(dbpool)

    def process_item(self, item, spider):
        # 使用twisted将mysql插入变成异步执行
        query = self.dbpool.runInteraction(self.do_insert, item)
        query.addErrback(self.handle_error, item, spider)  # 处理异常


    def handle_error(self, failure, item, spider):
        # 处理异步插入的异常
        print(failure)


    def do_insert(self, cursor, item):
        # 执行具体的插入
        # 根据不一样的item 构建不一样的sql语句并插入到mysql中
        insert_sql, params = item.get_insert_sql()
        print(insert_sql, params)
        try:
            cursor.execute(insert_sql, params)
        except Exception as e:
            print(e)
复制代码

注:

  • 以上代码定义的一个管道,是一个注入 MySQL 的管道,其中的写法模式彻底固定,只有其中的 dbpool = adbapi.ConnectionPool("pymysql", **dbparms) 此处须要与你使用的链接 mysql 的第三方库一致,好比此处我使用的是 pymysql,就设置 pymysql,若是使用的是其余第三方库,如 MySQLdb,更改成 MySQLdb 便可...
  • 其中的 dbparams 中的 mysql 信息,是从 settings 文件中读取的,咱们只须要配置 settings 文件中的 MySQL 信息便可

配置 settings.py 中的 pipeline 设置

因为咱们的 pipelines.py 中定义的这惟一一条管道的管道名称是建立 scrapy 项目时默认的。所以,settings.py 文件中已经默认了会使用该管道。

pipelines 默认设置

在此为了突出演示效果,我在下方列举了默认的 pipeline 与 其余自定义的 pipeline 的设置

pipeline 自定义设置

上面表示对于咱们设置了两个 pipeline,其中一个是写入 MySQL,而另外一个是写入 MongoDB,其中后面的 300,400 表示 pipeline 的执行优先级,其中数值越大优先级越小。

因为 pipelines 中用到的 MySQL 信息在 settings 中配置,在此也列举出,在 settings.py 文件的末尾添加以下信息便可

MYSQL_HOST = "localhost"
MYSQL_DBNAME = "crawler"
MYSQL_USER = "root"
MYSQL_PASSWORD = "root123"
复制代码

settings 中的 MySQL 设置

5 编写 spider

  接着使用水库放水的场景做为示例,水库放水给下游,但并非水库里的全部东西都须要,好比水库里有杂草,有泥石等等,这些东西若是不须要,那么就要把它过滤掉。而过滤的过程就相似于咱们的 spider 的处理过程。

  前面的配置都是辅助型的,spider 里面才是咱们爬取数据的逻辑,在 spiders 目录下建立一个 xicidaili.py 的文件,在里面编写咱们须要的 spider 逻辑。

  因为本文篇幅已通过于冗长,此处我打算省略 spider 中的详细介绍,能够从官网获取到 spider 相关信息,其中官方首页就有一个简单的 spider 文件的标准模板,而咱们要作的是,只须要按照模板,在其中编写咱们提取数据的规则便可。

# -*- coding: utf-8 -*-
import scrapy

from scrapy.http import Request
from proxy_ip.items import ProxyIpItem
from proxy_ip.util import DatetimeUtil


class ProxyIp(scrapy.Spider):
    name = 'proxy_ip'
    allowed_domains = ['www.xicidaili.com']
    # start_urls = ['http://www.xicidaili.com/nn/1']

    def start_requests(self):
        start_url = 'http://www.xicidaili.com/nn/'

        for i in range(1, 6):
            url = start_url + str(i)
            yield Request(url=url, callback=self.parse)

    def parse(self, response):
        ip_table = response.xpath('//table[@id="ip_list"]/tr')
        proxy_ip = ProxyIpItem()

        for tr in ip_table[1:]:
            # 提取内容列表
            country = tr.xpath('td[1]/img/@alt')
            ip = tr.xpath('td[2]/text()')
            port = tr.xpath('td[3]/text()')
            server_location = tr.xpath('td[4]/a/text()')
            is_anonymous = tr.xpath('td[5]/text()')
            protocol_type = tr.xpath('td[6]/text()')
            speed = tr.xpath('td[7]/div[1]/@title')
            connect_time = tr.xpath('td[8]/div[1]/@title')
            survival_time = tr.xpath('td[9]/text()')
            validate_time = tr.xpath('td[10]/text()')

            # 提取目标内容
            proxy_ip['country'] = country.extract()[0].upper() if country else ''
            proxy_ip['ip'] = ip.extract()[0] if ip else ''
            proxy_ip['port'] = port.extract()[0] if port else ''
            proxy_ip['server_location'] = server_location.extract()[0] if server_location else ''
            proxy_ip['is_anonymous'] = is_anonymous.extract()[0] if is_anonymous else ''
            proxy_ip['protocol_type'] = protocol_type.extract()[0] if type else ''
            proxy_ip['speed'] = speed.extract()[0] if speed else ''
            proxy_ip['connect_time'] = connect_time.extract()[0] if connect_time else ''
            proxy_ip['survival_time'] = survival_time.extract()[0] if survival_time else ''
            proxy_ip['validate_time'] = '20' + validate_time.extract()[0] + ':00' if validate_time else ''
            proxy_ip['source'] = 'www.xicidaili.com'
            proxy_ip['create_time'] = DatetimeUtil.get_current_localtime()

            yield proxy_ip
复制代码

6 middlewares.py

  不少状况下,咱们只须要编写或配置 items,pipeline,spidder,settings 这四个部分便可完整运行一个完整的爬虫项目,但 middlewares 在少数状况下会有用到。

  再用水库放水的场景为例,默认状况下,水库放水的流程大概是,自来水厂须要用水,因而他们发起一个请求给水库,水库收到请求后把阀门打开,按照过滤后的要求把水放给下游。但若是自来水厂有特殊要求,好比说自来水厂他能够只想要天天 00:00 - 7:00 这段时间放水,这就属于自定义状况了。

  而 middlewares.py 中就是定义的这些信息,它包括默认的请求与响应处理,好比默认全天放水... 而若是咱们有特殊需求,在 middlewares.py 定义便可... 如下附本项目中使用 fake-useragent 来随机切换请求的 user-agent 的代码:

class RandomUserAgentMiddleware(object):
    """ 随机更换 user-agent """
    def __init__(self, crawler):
        super(RandomUserAgentMiddleware, self).__init__()
        self.ua = UserAgent()
        self.ua_type = crawler.settings.get("RANDOM_UA_TYPE", "random")

 @classmethod
    def from_crawler(cls, crawler):
        return cls(crawler)

    def process_request(self, request, spider):
        def get_ua():
            return getattr(self.ua, self.ua_type)
        random_ua = get_ua()
        print("current using user-agent: " + random_ua)
        request.headers.setdefault("User-Agent", random_ua)
复制代码

settings.py 中配置 middleware 信息

# Crawl responsibly by identifying yourself (and your website) on the user-agent
RANDOM_UA_TYPE = "random"  # 能够配置 {'ie', 'chrome', 'firefox', 'random'...}

# Enable or disable downloader middlewares
# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
DOWNLOADER_MIDDLEWARES = {
    'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
    'proxy_ip.middlewares.RandomUserAgentMiddleware': 100,
}
复制代码

7 settings.py

settings.py 中配置了项目的不少信息,用于统一管理配置,下方给出示例:

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

import os
import sys
# Scrapy settings for proxy_ip project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
# http://doc.scrapy.org/en/latest/topics/settings.html
# http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
# http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html

BOT_NAME = 'proxy_ip'

SPIDER_MODULES = ['proxy_ip.spiders']
NEWSPIDER_MODULE = 'proxy_ip.spiders'


# Crawl responsibly by identifying yourself (and your website) on the user-agent
RANDOM_UA_TYPE = "random"  # 能够配置 {'ie', 'chrome', 'firefox', 'random'...}

# Obey robots.txt rules
ROBOTSTXT_OBEY = False

# Configure maximum concurrent requests performed by Scrapy (default: 16)
#CONCURRENT_REQUESTS = 32

# Configure a delay for requests for the same website (default: 0)
# See http://scrapy.readthedocs.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
DOWNLOAD_DELAY = 10
# The download delay setting will honor only one of:
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
#CONCURRENT_REQUESTS_PER_IP = 16

# Disable cookies (enabled by default)
COOKIES_ENABLED = False

# Disable Telnet Console (enabled by default)
#TELNETCONSOLE_ENABLED = False

# Override the default request headers:
#DEFAULT_REQUEST_HEADERS = {
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
# 'Accept-Language': 'en',
#}

# Enable or disable spider middlewares
# See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html
#SPIDER_MIDDLEWARES = {
# 'proxy_ip.middlewares.ProxyIpSpiderMiddleware': 543,
#}

# Enable or disable downloader middlewares
# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
DOWNLOADER_MIDDLEWARES = {
    'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
    'proxy_ip.middlewares.RandomUserAgentMiddleware': 100,
}

# Enable or disable extensions
# See http://scrapy.readthedocs.org/en/latest/topics/extensions.html
#EXTENSIONS = {
# 'scrapy.extensions.telnet.TelnetConsole': None,
#}

# Configure item pipelines
# See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
    'proxy_ip.pipelines.ProxyIpPipeline': 300,
}

BASE_DIR = os.path.dirname(os.path.abspath(os.path.dirname(__file__)))
sys.path.insert(0, os.path.join(BASE_DIR, 'proxy_ip'))


# Enable and configure the AutoThrottle extension (disabled by default)
# See http://doc.scrapy.org/en/latest/topics/autothrottle.html
AUTOTHROTTLE_ENABLED = True
# The initial download delay
#AUTOTHROTTLE_START_DELAY = 5
# The maximum download delay to be set in case of high latencies
#AUTOTHROTTLE_MAX_DELAY = 60
# The average number of requests Scrapy should be sending in parallel to
# each remote server
#AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# Enable showing throttling stats for every response received:
AUTOTHROTTLE_DEBUG = True

# Enable and configure HTTP caching (disabled by default)
# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
#HTTPCACHE_ENABLED = True
#HTTPCACHE_EXPIRATION_SECS = 0
#HTTPCACHE_DIR = 'httpcache'
#HTTPCACHE_IGNORE_HTTP_CODES = []
#HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'


MYSQL_HOST = "localhost"
MYSQL_DBNAME = "crawler"
MYSQL_USER = "root"
MYSQL_PASSWORD = "root123"
复制代码

6、运行测试

在 proxy_ip 项目的根目录下,建立一个 main.py 做为项目运行的入口文件,其中代码以下:

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

__author__ = 'jiangzhuolin'

import sys
import os
import time

while True:
    os.system("scrapy crawl proxy_ip")  # scrapy spider 的启动方法 scrapy crawl spider_name
    print("程序开始休眠...")
    time.sleep(3600)  # 休眠 1 小时后继续爬取

复制代码

右键 "run main" 查看运行效果

程序运行效果

7、总结

  个人原来打算是写一篇 scrapy 简单项目的详细介绍,把里面各类细节都经过我的的理解分享出来。但很是遗憾,因为经验不足,致使越写越以为篇幅会过于冗长。所以里面有大量信息被我简化或者直接没有写出来,本文可能不适合彻底小白的新手,若是你写过简单的 scrapy 项目,但对其中的框架不甚理解,我但愿能经过本文有所改善。

  本项目代码已提交到我我的的 github 与 码云上,若是访问 github 较慢,能够访问码云获取完整代码,其中,包括了建立 MySQL 数据库表的 SQL 代码

github 代码地址:github.com/jiangzhuoli…

码云代码地址:gitee.com/jzl975/prox…

相关文章
相关标签/搜索