爬虫 - Scrapy 爬取某招聘网站

时间:2024-10-06 07:43:15

文章目录

    • 项目简介
    • 一、创建项目
      • 1、终端创建项目
      • 2、修改配置
    • 二、爬取列表数据
      • 1、数据分析
      • 2、模型建立
      • 3、存储为 json 数据
      • 4、存储为 mysql 数据
    • 三、爬取列表下一页及所有数据
      • 1、特征分析
      • 2、编写方法
    • 四、图片
      • 1、添加图片保存地址
      • 2、添加图片请求
      • 3、添加图片管道
    • 五、爬取详情
    • 六、添加下载中间件
      • 1、代理 USER_AGENT
      • 2、IP 池 PROXIES
    • 七、设置日志
      • 1、设置日志级别
      • 2、设置日志保存地址


项目简介

eleduck 电鸭 是一款远程工作的招聘交流网站。这里仅做学习使用。


一、创建项目

1、终端创建项目

$ scrapy startproject WebScrapy  # 创建项目
$ tree

$ cd WebScrapy  # 进入项目文件
$ scrapy genspider eleduck "" # 创建爬虫
$ tree
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

# 检查爬虫
$ scrapy check eleduck  # 此处根据爬虫的名字来区分,而非文件名
----------------------------------------------------------------------
Ran 0 contracts in 0.000s

OK

# 运行爬虫
$ scrapy crawl eleduck
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

2、修改配置

中:

# 1 修改 USER_AGENT,防止被反扒
USER_AGENT = 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50'

# 2、设置不遵守 robots 协议
ROBOTSTXT_OBEY = False 
  • 1
  • 2
  • 3
  • 4
  • 5

二、爬取列表数据

1、数据分析

在这里插入图片描述

列表中包含 标题,发言人 等信息,可通过 xpath 找到


2、模型建立

已自动的创建的 WebscrapyItem 类下添加字段

class WebscrapyItem(scrapy.Item):
    # define the fields for your item here like:
    writer = scrapy.Field()  # 作者
    title = scrapy.Field()	# 招聘标题
    detailUrl = scrapy.Field()  # 详情地址
    iconUrl = scrapy.Field()   # 头像 icon 地址
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

3、存储为 json 数据

中添加存储方法

import json

class WebscrapyPipeline:

    def __init__(self):
        self.f = open('','w')

    def process_item(self, item, spider):

        # print('writer : ', item['writer'])
        # print(dict(item))
        content = json.dumps(dict(item), ensure_ascii=False) + ',\n'
        # num = \
        self.f.write(content)
        # print('num : ', num)
        return item

    def close_spider(self, spider):
        self.f.close()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

4、存储为 mysql 数据

import pymysql


# 保存到 mysql
class WebscrapyPipeline:

    def __init__(self):

        self.conn = pymysql.connect("localhost", "root", "123456Aa*", "uinfo")

        self.cursor = self.conn.cursor()

        sql = '''
            CREATE TABLE IF NOT EXISTS  ed_list(
            id INT PRIMARY KEY AUTO_INCREMENT,
        	writer VARCHAR(100),
        	title VARCHAR(100),
        	detailUrl VARCHAR(100) )
            '''

        ret = self.cursor.execute(sql)
        print('创建数据表 : ', ret)


    def process_item(self, item, spider):
 
        sql = """ INSERT INTO ed_list(writer, title, detailUrl)  \
                    VALUES ('%s', '%s', '%s')""" \
                    % (item['writer'], item['title'], item['detailUrl'])

        # print(sql)
        try:
            # 执行sql语句
            self.cursor.execute(sql)
            # 执行sql语句
            self.conn.commit()
            print('插入数据 执行成功')
        except:
            # 发生错误时回滚
            self.conn.rollback()
            print('插入数据 执行失败')
 
        return item

    def close_spider(self, spider):
        self.conn.close()
  • 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

三、爬取列表下一页及所有数据

1、特征分析

当有下一页的时候,下一页这个 li 的 aria-disabled 属性为 false;最后一页时,这个属性为 true;

且下一级的 a 标签 href 属性有连接信息;拼接本站域名,刚好是下一页的url,如 /?page=96

在这里插入图片描述

在这里插入图片描述


2、编写方法

在 的 parse 方法中添加如下语句,判断是否有下一页,以及下一页的 url;

并发送下一页请求进行爬取。


        # 检查列表是否有下一页
        existNext = htmlTree.xpath('//li[@title="下一页"]/@aria-disabled')[0]  # false
        existNext_str = str(existNext)

        print('existNext' ,existNext)
        if existNext_str == 'false': # 有下一页,继续请求
            next_page = htmlTree.xpath('//li[@title="下一页"]/a/@href')[0]  # /?page=95

            print('=' * 30, '\n', next_page)
            url = urlPrefix + next_page
            yield scrapy.Request(url, callback= self.parse)  # 发送请求,并由本 parse 方法继续处理
        else:
            print('✔️' * 20,' 不存在下一页,爬取结束 ')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

四、图片

1、添加图片保存地址

在 中添加图片保存地址字段

# IMAGES_STORE = '/Users/user/Desktop/008/'  # 绝对地址
IMAGES_STORE = 'rsc/pics/'  # 相对地址,图片将位于 位于主目录的 rsc/pics/full 中
  • 1
  • 2

2、添加图片请求

中添加 IconPipeline 类,处理得到的 item,并发送请求

继承自 ImagesPipeline,重写 get_media_requests 处理

重写 item_completed 方法,处理图片下载完成


import os
from WebScrapy.settings import IMAGES_STORE as imges_store
from scrapy.pipelines.images import ImagesPipeline

class IconPipeline(ImagesPipeline):

    def get_media_requests(self, item, info):
        print('\n-- IconPipeline process_item', item)
        iconUrl = item['iconUrl']
        yield scrapy.Request(iconUrl)


    # 下载完成,重命名
    def item_completed(self, results, item, info):

        print('\n-- item_completed, results : ', results, ' info : ', info )
        '''
-- item_completed, results :  [(True, {'url': '/eleduck/avatar/!64', 'path': 'full/', 'checksum': '41cf3ebf2bf417f5957e642176565b37', 'status': 'downloaded'})]  

info :  < object at 0x7fd416c4c390>  
 
        '''

        dict = [x for ok, x in results if ok][0]
        print('\n-- dict : ', dict)

        path = dict['path']
        print('-- path : ', path)
        originPath = images_store + path
        print('-- originPath : ', originPath)

        writer = item['writer']
        targetPath =  images_store + writer + '.jpg'
        os.rename(originPath, targetPath)

        return 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

3、添加图片管道

将 IconPipeline 添加到 settings 的 ITEM_PIPELINES

ITEM_PIPELINES = {
   '': 300,
   '': 300,
}
  • 1
  • 2
  • 3
  • 4

运行爬虫后,会得到结果:

在这里插入图片描述


五、爬取详情

在列表中可以抓取到详情的地址,即 detailUrl 保存的内容;拼接本站url地址即详情页;

所以在获取 detailUrl 的时候,直接发出请求,让另一个parse 方法来处理即可;

本次详情数据直接保存到了 html 文件中。

修改 文件:

urlPrefix = ''

class EleduckSpider(scrapy.Spider):
    ...

    def parse(self, response):
        for node in list:
            ...
            detailUrl = subnode.xpath('h2/a/@href')[0]
            ...
            yield scrapy.Request(urlPrefix+detailUrl, callback=self.parseDetail) # 发送详情请求
            yield item
            
            
     
    # 处理详情
    def parseDetail(self, response):

        print('\n-- parseDetail : ', response.url, type(response.url))

        arr = str(response.url).split('/')

        print(arr)
        # print(arr(-1)

        path = 'rsc/details/' + arr[-1] + '.html'
        print('-- detailFilePath : ', path)

        html = response.text

        with open(path, 'w') as f:
            f.write(html)
            print('详情保存成功:', path)
  • 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

在这里插入图片描述


六、添加下载中间件

1、代理 USER_AGENT

1) 中添加 USER_AGENTS 键值对

USER_AGENTS = ['Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50',
               'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1',
               'User-Agent:Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11',
               'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11']
  • 1
  • 2
  • 3
  • 4

2)在 中添加 下载中间件类

创建类的时候,不能确定是 下载中间件 还是 中间件,由下一步在 setting 中配置决定。

实现 下载中间件的核心方法

import random

class DBDownloaderMiddleware:
 
    def process_request(self, request, spider):

        ua = random.choice(spider.settings['USER_AGENTS'])
        request.headers['User-Agent'] = ua
     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

3)将 DBDownloaderMiddleware 添加到 DOWNLOADER_MIDDLEWARES 键中

DOWNLOADER_MIDDLEWARES = {
   # '': 543,
   '': 544
}
  • 1
  • 2
  • 3
  • 4

4)在返回中检测 ua 是否设置成功

可以在 中添加 下载中间件类,检测返回的内容

# 检查 ua
class CheckRespDownloaderMiddleware:

    def process_response(self, request, response, spider):
        print(request.headers['User-Agent'])
        return response
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

同样需要添加到 DOWNLOADER_MIDDLEWARES 键中

DOWNLOADER_MIDDLEWARES = {
   # '': 543,
   '': 544,
   '': 545
}
  • 1
  • 2
  • 3
  • 4
  • 5

2、IP 池 PROXIES

方法和上面雷同

1)中添加 PROXIES 键值对

PROXIES=[
  {'ip_port':'http://171.35.12.213:9999' },
  {'ip_port':'http://171.35.12.212:9999', "user_password":"username:password"},
  {'ip_port':'http://171.35.12.22:9999', "user_password":"root:admin123456"},
]
  • 1
  • 2
  • 3
  • 4
  • 5

2)在上述下载中间件类 DBDownloaderMiddleware 中添加代码

代码变为

class DBDownloaderMiddleware:

    def process_request(self, request, spider):

        # 设置 user-agent
        ua = random.choice(spider.settings['USER_AGENTS'])
        request.headers['User-Agent'] = ua

        # 设置 proxy
        proxy = random.choice(spider.settings['PROXIES'])
        if proxy['user_password'] is None:
            request.meta['proxy'] = proxy['ip_port']
        else:
            upwd = base64.b64encode(proxy['user_password'])

            # 令牌
            request.headers['Proxy-Authorization'] = 'Basic '+ upwd
            request.meta['proxy'] = proxy['ip_port']

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

七、设置日志

1、设置日志级别

中添加 LOG_LEVEL 键值对,表明日志级别

LOG_LEVEL = 'WARNING'
  • 1

Python 的内置日志记录定义了5个不同的级别来指示给定日志消息的严重性。以下是标准的,按降序排列:

DEBUG - 调试信息

INFO - 一般信息

WARNING - 警告信息

ERROR - 一般错误

CRITICAL - 严重错误


2、设置日志保存地址

中添加 LOG_LEVEL 键值对,表明日志记录文件的地址

LOG_FILE=''
  • 1