【Scrapy】爬取阳光长跑信息

利用爬虫框架Scrapy来爬取学校阳光长跑个人信息与长跑信息,并储存在MongoDB中。

具体步骤:

以下简述具体的爬取逻辑与代码,该项目源代码

学习scrapy最好的方式为看官方文档,详见

  • 创建Scrapy项目。

    scrapy startproject sunny_sport

  • 创建爬虫。

    1
    2
    3
    # 该命令样式
    # scrapy genspider mydomain mydomain.com
    scrapy genspider sunnysport hzaspt.sunnysport.org.cn
  • 配置该项目相关配置。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    # sunny_sport/sunny_sport/settings.py
    # 以下为添加的配置
    # 不遵循 robots.txt 的规则
    ROBOTSTXT_OBEY = False
    # 开启 cookies 的 debug
    COOKIES_DEBUG = True
    # 设置请求头信息
    DEFAULT_REQUEST_HEADERS = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'zh-CN,zh;q=0.8,en;q=0.6',
    'User-Agent': 'Mozilla/5.0 (Macintosh;'
    ' Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko)'
    ' Chrome/57.0.2987.133 Safari/537.36'
    }
    # 激活 MongoDB 的 pipeline
    ITEM_PIPELINES = {
    'sunny_sport.pipelines.MongoPipeline': 300,
    }
  • 编写爬取的个人信息Item与长跑信息Item

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    # sunny_sport/sunny_sport/items.py
    # -*- coding: utf-8 -*-
    from scrapy import Item, Field
    class StudentItem(Item):
    """
    name 姓名
    student_num 学号
    total_mileage 总里程
    total_avg_speed 总平均速度
    valid_cnt 有效次数
    """
    name = Field()
    student_num = Field()
    total_mileage = Field()
    total_avg_speed = Field()
    valid_cnt = Field()
    class SportItem(Item):
    """
    cnt 长跑次数
    date 长跑日期
    time 长跑时段
    mileage 长跑里程
    avg_speed 长跑平均速度
    is_valid 长跑是否有效 (1有效 0无效)
    """
    cnt = Field()
    date = Field()
    time = Field()
    mileage = Field()
    avg_speed = Field()
    is_valid = Field()
  • 编写爬虫逻辑代码。

    需要注意的是,Scrapy框架会自动处理你的cookie,详见CookiesMiddleware

    只需要在请求的时候在 meta 添加 cookiejar 即可。

    不过在该项目使用之后出现了302重定向,即未取得cookie值,目前尚未通晓什么原因,后续更新…

    因此,在该项目中我先登录一遍页面获取了cookie值后直接添加请求中,略显笨拙。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    # sunny_sport/sunny_sport/spiders/sunnysport.py
    # -*- coding: utf-8 -*-
    from scrapy.http import Request, FormRequest
    from scrapy.spiders import CrawlSpider
    from sunny_sport.items import StudentItem, SportItem
    class SunnysportSpider(CrawlSpider):
    name = "sunnysport"
    allowed_domains = ["hzaspt.sunnysport.org.cn"]
    start_url = 'http://hzaspt.sunnysport.org.cn/runner/index.html'
    def start_requests(self):
    """
    模拟表单登录
    """
    return [FormRequest(
    url='http://hzaspt.sunnysport.org.cn/login/',
    formdata={
    'username': '2015002507',
    'password': '2015002507'
    },
    # meta={'cookiejar': 1},
    callback=self.after_login
    )]
    def after_login(self, response):
    """
    登录后,爬取基本信息页面和长跑信息页面
    """
    return [Request(
    url=self.start_url,
    # meta={'cookiejar': response.meta['cookiejar']},
    cookies={'sessionid': 'clyid4a5s1c81hpygz2a26iqovla78jz'},
    dont_filter=True,
    callback=self.parse_main_page
    )]
    def parse_main_page(self, response):
    """
    解析个人信息页面
    """
    name = response.xpath('//div[@class="col-md-3"][1]/div[1]/div[2]/label/text()').extract_first()
    student_num = response.xpath('//div[@class="col-md-3"][1]/div[1]/div[3]/label/text()').extract_first()
    total_mileage = response.xpath('//div[@class="col-md-3"][2]/div[1]/div[2]//tr[1]/td[2]/text()').extract_first()
    total_avg_speed = response.xpath(
    '//div[@class="col-md-3"][2]/div[1]/div[2]//tr[2]/td[2]/text()').extract_first()
    valid_cnt = response.xpath('//div[@class="col-md-3"][2]/div[1]/div[2]//tr[3]/td[2]/text()').extract_first()
    yield StudentItem(
    name=name,
    student_num=student_num,
    total_mileage=total_mileage,
    total_avg_speed=total_avg_speed,
    valid_cnt=valid_cnt
    )
    yield Request(
    url='http://hzaspt.sunnysport.org.cn/runner/achievements.html',
    # meta={'cookiejar': response.meta['cookiejar']},
    cookies={'sessionid': 'clyid4a5s1c81hpygz2a26iqovla78jz'},
    callback=self.parse_sport_page
    )
    def parse_sport_page(self, response):
    """
    解析长跑信息
    """
    trs = response.xpath('//div[@class="col-md-8"]//tbody/tr')
    for tr in trs:
    cnt = tr.xpath('.//td[1]/text()').extract_first()
    date = tr.xpath('.//td[2]/text()').extract_first()
    time = tr.xpath('.//td[3]/text()').extract_first()
    mileage = tr.xpath('.//td[4]/text()').extract_first()
    avg_speed = tr.xpath('.//td[5]/text()').extract_first()
    is_valid = tr.xpath('.//td[6]/span[contains(@class,"glyphicon-ok")]').extract_first() and 1 or 0
    yield SportItem(
    cnt=cnt,
    date=date,
    time=time,
    mileage=mileage,
    avg_speed=avg_speed,
    is_valid=is_valid
    )
  • 将爬取的数据储存进MongoDB中。

    pipeline就像管道一样,将你之前定义的Item输入具体目的地。

    使用前要安装MongoDB与pymongo包。

    如何在Mac下使用MongoDB,请参见我之前的博文

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    # sunny_sport/sunny_sport/pipelines.py
    # -*- coding: utf-8 -*-
    import pymongo
    class MongoPipeline(object):
    collection_name = 'sunny_sport'
    def __init__(self, mongo_uri, mongo_db):
    self.mongo_uri = mongo_uri
    self.mongo_db = mongo_db
    @classmethod
    def from_crawler(cls, crawler):
    return cls(
    mongo_uri=crawler.settings.get('MONGO_URI'),
    mongo_db=crawler.settings.get('MONGO_DATABASE', 'test')
    )
    def open_spider(self, spider):
    self.client = pymongo.MongoClient(self.mongo_uri)
    self.db = self.client[self.mongo_db]
    def close_spider(self, spider):
    self.client.close()
    def process_item(self, item, spider):
    self.db[self.collection_name].insert(dict(item))
    return item
  • 运行该项目。

    在终端输入 scrapy crawl sunnysport 即可。