爬虫项目编写流程:

  1. 创建项目:scrapy project 项目名称
  2. 创建爬虫名称:scrapy genspider 爬虫名称 "限制域"
  3. 明确需求:编写items.py
  4. 编写spiders/xxx.py,编写爬虫文件,处理请求和响应,以及提取数据(yield item)
  5. 编写pipelines.py,编写管道文件,处理spider返回的item数据,比如本地持久化存储等
  6. 编写settings.py,启动管道组件:ITEM_PIPELINES={},以及其它header等中间件设置
  7. 执行爬虫:scrapy crawl 爬虫名称

1.创建项目

scrapy startproject Tencent
cd Tencent # 进入到项目目录

 

2.创建一个爬虫

scrapy genspider tencent.job "www.tencent.com"

在引用模块、settings配置时,不是从项目文件夹开始,面是从项目文件夹的下一层开始。

3.打开网址,查看数据:https://hr.tencent.com/position.php?keywords=python&lid=2218&tid=87&start=50#a

4.明确需求,要爬取的字段,编写Tencent/items.py:

# Tencent/Tencent/items.py
import scrapy
class TencentItem(scrapy.Item):
    # 职位:
    positionName = scrapy.Field()
    # 职位详情链接:
    positionLink = scrapy.Field()
    # 职位类别
    positionType = scrapy.Field()
    # 招聘人数
    peopleNumber = scrapy.Field()
    # 工作地址
    workLocation = scrapy.Field()
    # 发布时间
    publishTime = scrapy.Field()

 

5.编写爬虫,Tencent/spiders/tencent_job.py:

import scrapy
class TencentJobSpider(scrapy.Spider):
    name = "tencent.job"
    allowed_domains = ["tencent.com"]
    base_url = "https://hr.tencent.com/position.php?keywords=python&lid=2218&tid=87&start="
    offset = 0  # 起始页start=0,每10个换一页
    start_url = [base_url + str(offset)]

 

分析页面,获取我们想要的字段。

在我们想要的信息中,点击检查,查看elements,可知节点为:

//tr[@class='even']

或者 //tr[@class='odd']

将两者合在一起: //tr[@class='even'] |  //tr[@class='odd']

 

每个tr下面,是td列表,这些td列表就是每个职位的相关信息。

职位名称: //tr[@class='even'] | /td[1]/a/text()

注意:xpath的下标是从1开始

职位详情链情: //tr[@class='even'] | /td[1]/a/@href

............

import scrapy
from Tencent.items import TencentItem
class TencentJobSpider(scrapy.Spider):
    name = "tencent.job"
    allowed_domains = ["tencent.com"]
    base_url = "https://hr.tencent.com/position.php?keywords=python&lid=2218&tid=87&start="
    offset = 0  # 起始页start=0,每10个换一页
    start_url = [base_url + str(offset)]

    def parse(self, response):
        node_list = response.xpath("//tr[@class='even'] |  //tr[@class='odd']")
        # response.xpath之后的对象,仍然是xpath对象
        for node in node_list:
            #xpath方法之后,返回的仍然是xpath对象;xpath的extract()方法,以unicode编码返回xpath对象的内容
            positionName = node.xpath('./td[1]/a/text()').extract()
            # 注意要在td前面加上"./",表示此td是node_list的下一级
            positionLink = node.xpath('./td[1]/a/@href').extract()
            positionType = node.xpath('./td[2]/text()').extract()
            peopleNumber = node.xpath('./td[3]/text()').extract()
            workLocation = node.xpath('./td[4]/text()').extract()
            publishTime = node.xpath('./td[5]/text()').extract()
            item = TencentItem()
            # xpath及其extract返回的都是列表,因此取其第一个元素;并使用encode("utf-8")编码为utf8
            item['positionName'] = positionName[0].encode("utf-8") if positionName else ""
            item['positionType'] = positionType[0].encode("utf-8") if positionType else ""
            item['positionLink'] = positionLink[0].encode("utf-8") if positionLink else ""
            item['peopleNumber'] = peopleNumber[0].encode("utf-8") if peopleNumber else ""
            item['workLocation'] = workLocation[0].encode("utf-8") if workLocation else ""
            item['publishTime'] = publishTime[0].encode("utf-8") if publishTime else ""
            yield item
        if self.offset < 2190:
            self.offset += 10 # 每页10条数据
            url = self.base_url + self.offset
            # 构建下一页的url请求对象
            yield scrapy.Request(url, callback=self.parse)
            # 1.如果下面的请求内容不一样,则需要自己再写一个回调方法,回调自定义的方法

            # 2.这里返回的是url对象,引擎接受到以后判断它不是item对象,不会发送给管道处理;
            # 是Request对象,引擎将发送给调度器,去请处理请求

            # 3.这里要使用yield,不断的返回,直到self.offset >=2190。

 

调用管道时,注意:

每个response对应一个parse()方法;for循环中,循环一次,生成一个item对象;for循环中所有item对象,对应一个管道process_item()方法。

即for循环中的有item对象,共用一个管道对象process_item();即管道类只会实例化一次,多次调用process_item()方法,调用process_items()时,都是一个管道类对象;因此,也只会初始化一次,也只需要打开和关闭文件一次。

6.编写管道文件:保存数据

1).编写管道类:Tencent/pipelines.py

import json
class TencentPipeline(object):
    def __init__(self):
        self.f = open("tencent.json", "w")
    def process_item(self, item, spider):
        # 要处理中文,ensure_ascii要改为False
        content = json.dumps(dict(item), ensure_ascii=False) + ",\n"
        # 此时,不再需要encode("utf-8"),因为在得到item的时侯已经encode("utf-8")
        # 总之,从网络请求来的数据,要encode("utf-8")编码一次。
        self.f.write(content)
        return item

    def close_spider(self, spider):
        self.f.close()

 

2)在Tencent/settings.py文件中,启用管道,并将以上管道类加入其中

ITEM_PIPELINES = {
   'Tencent.pipelines.TencentPipeline': 300,
}

 

3).确保爬虫文件Tencent/spiders/xx.py中的爬虫类的parse()方法,返回的item类的数据,才可以使用管道。

 7.执行爬虫:

crapy crawl 爬虫名称

 

8.优化:

以上示例中,总页数是假设的;适用于没有下一页标签的网站。

而本项目中,实际上网站有下一页可点击,当到最后一页的时侯,下一页不可点击。

因此,可以根据下一而是否可以点击,来判断是否是最后一页。

查看页面,得到下一页的节点为://a[@id='text']/@href

 

最后一页节点的href内容为"javascript:;",其标签为://a[@class='noative']

但是,第一页的前一页的标签也为://a[@class='noative']

但是id不一样,第一页的前一页的id为'prev'

 

因此,综合这两个条件: //a[@class='noative' and @id='next']

yield item
        if not response.xpath("//a[@class='noative' and @id='next']"):
            url = response.xpath("//a[@id='next']/@href").extract()[0]
            # 构建下一页的url请求对象
            yield scrapy.Request(self.base_url + url, callback=self.parse)

 注意:同一个标签内,xpah可以用and,or表示;不是同一个标签内,要使用 | &表示