1. 程式人生 > 程式設計 >Python爬蟲例項——scrapy框架爬取拉勾網招聘資訊

Python爬蟲例項——scrapy框架爬取拉勾網招聘資訊

本文例項為爬取拉勾網上的python相關的職位資訊,這些資訊在職位詳情頁上,如職位名,薪資,公司名等等.

分析思路

分析查詢結果頁

在拉勾網搜尋框中搜索'python'關鍵字,在瀏覽器位址列可以看到搜尋結果頁的url為: 'https://www.lagou.com/jobs/list_python?labelWords=&fromSearch=true&suginput=',嘗試將?後的引數刪除,發現訪問結果相同.

開啟Chrome網頁除錯工具(F12),分析每條搜尋結果(即每個職位)在html中所處的元素定位,發現每條結果都在<ul class="item_con_list">下的li標籤中.

Python爬蟲例項——scrapy框架爬取拉勾網招聘資訊

因為我們需要每個職位的具體資訊,因此需要獲取到每條搜尋結果的詳情url,即點選搜尋結果後進入的詳情頁的url.

繼續檢視li標籤中的元素,找到想要的詳情url,找到後的url為: href=https://www.lagou.com/jobs/6945237.html?show=b6e8e778fcae4c2aa2111ba58f9ebfa0

Python爬蟲例項——scrapy框架爬取拉勾網招聘資訊

檢視其它搜尋結果的詳情url,發現其格式都為: href="https://www.lagou.com/jobs/{某個id}.html?show={show_id}" rel="external nofollow"

對於第一個ID,每條結果的id都不一樣,猜想其為標記每個職位的唯一id,對於show_id,每條結果的id都是一樣的,嘗試刪除show引數,發現一樣可以訪問到具體結果詳情頁

那麼我們直接通過xpath提取到每個職位的第一個ID即可,但是除錯工具的elements標籤下的html是最終網頁展示的html,並不一定就是我們訪問 https://www.lagou.com/jobs/list_python 返回的response的html,因此點到Network標籤,重新重新整理一下頁面,找到 https://www.lagou.com/jobs/list_python 對應的請求,檢視其對應的response,搜尋 'position_link'(即前面我們在elements中找到的每條搜尋結果的詳情url),發現確實返回了一個網址,但是其重要的兩個ID並不是直接放回的,而是通過js生成的,說明我們想要的具體資料並不是這個這個請求返回的.

Python爬蟲例項——scrapy框架爬取拉勾網招聘資訊

那麼我們就需要找到具體是那個請求會返回搜尋結果的資訊,一般這種情況首先考慮是不是通過ajax獲取的資料,篩選型別為XHR(ajax)的請求,可以逐個點開檢視response,發現 positionAjax.json 返回的資料中就存在我們想要的每條搜尋結果的資訊. 說明確實是通過ajax獲取的資料,其實點選下一頁,我們也可以發現位址列url地址並沒有發生變化,只是區域性重新整理了搜尋結果的資料,也說明了搜尋結果是通過ajax返回的.

Python爬蟲例項——scrapy框架爬取拉勾網招聘資訊

分析上面ajax的response,檢視其中是否有我們想要的職位ID,在preview中搜索之前在elements中找到的某個職位的url的兩個ID,確實兩個ID都存在response中,分析發現第一個ID即為positionId,第二個即為showId,我們還可以發現response中返回了當前的頁碼數pageNo

因此我們只需要訪問上面ajax對應的url: https://www.lagou.com/jobs/positionAjax.json?needAddtionalResult=false 就可以拿到我們想要的ID,然後填入詳情url模板: https://www.lagou.com/jobs/{position_id}.html?show={show_id}中即可訪問詳情頁了.

但是當我們直接訪問 https://www.lagou.com/jobs/positionAjax.json?needAddtionalResult=false 時,返回的結果卻是: {"status":false,"msg":"您操作太頻繁,請稍後再訪問","clientIp":"139.226.66.44","state":2402}

Python爬蟲例項——scrapy框架爬取拉勾網招聘資訊

經過百度查詢後發現原來直接訪問上述地址是不行的,這也是拉鉤的一個反爬策略,需要我們帶上之前訪問查詢結果頁(https://www.lagou.com/jobs/list_python?)的cookie才行,因為我們這裡使用的是scrapy框架,該框架是能夠自帶上次請求的cookie來訪問下一個請求的,所以我們這裡不需要手動去新增cookie資訊,只需要首先訪問一下查詢結果頁就可以了. 即start_url = https://www.lagou.com/jobs/list_python

此外發現這個ajax請求是通過POST方式傳送的,因此還需要分析它提交的form資料,在第一頁中有三條資料資訊,first為true,pn為1 kd為python,第二頁中first為false,pn為2,kd同樣為python,且多了一個sid

分析這四個引數,第一個first為表示是否是第一頁,第二個pn為表示當前頁碼數,第三個kd為表示搜尋的關鍵字,第四個sid經過和上面showId對比發現其值就為showId

Python爬蟲例項——scrapy框架爬取拉勾網招聘資訊

分析職位詳情頁

前面分析完後就可以拼接出職位詳情頁url了,點開詳情頁,同樣的思路分析我們想要的資料是不是就在詳情頁的url中,這裡想要職位名稱,工資,地點,經驗,關鍵字,公司資訊等

Python爬蟲例項——scrapy框架爬取拉勾網招聘資訊

在network中查詢對應的response,發現數據確實就存在response中,因此直接通過xpath就可以提取想要的資料了

編寫爬蟲程式碼

具體程式碼在github:

這裡只放出關鍵程式碼

建立scrapy專案

scrapy startproject LaGou

建立爬蟲

scrapy genspider lagou www.lagou.com

編寫items.py,設定要想爬取的欄位

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

# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy


class LagouItem(scrapy.Item):
  # define the fields for your item here like:
  job_url = scrapy.Field()
  job_name = scrapy.Field()
  salary = scrapy.Field()
  city = scrapy.Field()
  area = scrapy.Field()
  experience = scrapy.Field()
  education = scrapy.Field()
  labels = scrapy.Field()
  publish_date = scrapy.Field()
  company = scrapy.Field()
  company_feature = scrapy.Field()
  company_public = scrapy.Field()
  company_size= scrapy.Field()

編寫爬蟲程式碼 lagou.py

# -*- coding: utf-8 -*-
import scrapy
from LaGou.items import LagouItem
import json
from pprint import pprint
import time


class LagouSpider(scrapy.Spider):
  name = 'lagou'
  allowed_domains = ['www.lagou.com']
  start_urls = ['https://www.lagou.com/jobs/list_python?']

  def __init__(self):
    # 設定頭資訊,若不設定的話,在請求第二頁時即被拉勾網認為是爬蟲而不能爬取資料
    self.headers = {
      "Accept": "application/json,text/javascript,*/*; q=0.01","Connection": "keep-alive","Host": "www.lagou.com","Referer": 'https://www.lagou.com/jobs/list_Python?',"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8","referer": "https://www.lagou.com/jobs/list_python?"
    }
    self.sid = ''
    self.job_url_temp = 'https://www.lagou.com/jobs/{}.html?show={}'
    # 清空檔案
    with open('jobs.json','w') as f:
      f.truncate()

  def parse(self,response):
    """
    解析起始頁
    """
    # response為GET請求的起始頁,自動獲取cookie
    # 提交POST帶上前面返回的cookies,訪問資料結果第一頁
    yield scrapy.FormRequest(
      'https://www.lagou.com/jobs/positionAjax.json?needAddtionalResult=false',callback=self.parse_list,formdata={"first": "false","pn": "1","kd": "python",},headers=self.headers
    )
  def parse_list(self,response):
    """
    解析結果列表頁的json資料
    """
    # 獲取返回的json,轉為字典
    res_dict = json.loads(response.text)
    # 判斷返回是否成功
    if not res_dict.get('success'):
      print(res_dict.get('msg','返回異常'))
    else:
      # 獲取當前頁數
      page_num = res_dict['content']['pageNo']
      print('正在爬取第{}頁'.format(page_num))
      # 獲取sid
      if not self.sid:
        self.sid = res_dict['content']['showId']
      # 獲取響應中的職位url字典
      part_url_dict = res_dict['content']['hrInfoMap']
      # 遍歷職位字典
      for key in part_url_dict:
        # 初始化儲存職位的item
        item = LagouItem()
        # 拼接完整職位url
        item['job_url'] = self.job_url_temp.format(key,self.sid)
        # 請求職位詳情頁
        yield scrapy.Request(
          item['job_url'],callback=self.parse_detail,headers=self.headers,meta={'item': item}
        )

      # 獲取下一頁
      if page_num < 30:
        # time.sleep(2)
        yield scrapy.FormRequest(
          'https://www.lagou.com/jobs/positionAjax.json?needAddtionalResult=false',"pn": str(page_num+1),"sid": self.sid
               },headers=self.headers
        )

  def parse_detail(self,response):
    """
    解析職位詳情頁
    """
    # 接收item
    item = response.meta['item']
    # 解析資料
    # 獲取職位頭div
    job_div = response.xpath('//div[@class="position-content-l"]')
    if job_div:
      item['job_name'] = job_div.xpath('./div/h1/text()').extract_first()
      item['salary'] = job_div.xpath('./dd/h3/span[1]/text()').extract_first().strip()
      item['city'] = job_div.xpath('./dd/h3/span[2]/text()').extract_first().strip('/').strip()
      item['area'] = response.xpath('//div[@class="work_addr"]/a[2]/text()').extract_first()
      item['experience'] = job_div.xpath('./dd/h3/span[3]/text()').extract_first().strip('/').strip()
      item['education'] = job_div.xpath('./dd/h3/span[4]/text()').extract_first().strip('/').strip()
      item['labels'] = response.xpath('//ul[@class="position-label clearfix"]/li/text()').extract()
      item['publish_date'] = response.xpath('//p[@class="publish_time"]/text()').extract_first()
      item['publish_date'] = item['publish_date'].split('&')[0]
      # 獲取公司dl
      company_div = response.xpath('//dl[@class="job_company"]')
      item['company'] = company_div.xpath('./dt/a/img/@alt').extract_first()
      item['company_feature'] = company_div.xpath('./dd//li[1]/h4[@class="c_feature_name"]/text()').extract_first()
      item['company_feature'] = item['company_feature'].split(',')
      item['company_public'] = company_div.xpath('./dd//li[2]/h4[@class="c_feature_name"]/text()').extract_first()
      item['company_size'] = company_div.xpath('./dd//li[4]/h4[@class="c_feature_name"]/text()').extract_first()
      yield item

編寫middlewares.py,自定義downloadermiddleware,用來每次傳送請求前,隨機設定user-agent,這裡使用了第三方庫 fake_useragent,能夠隨機提供user-agent,使用前先安裝: pip install fake_useragent

from fake_useragent import UserAgent
import random

class RandomUserAgentDM:
  """
  隨機獲取userAgent
  """
  def __init__(self):
    self.user_agent = UserAgent()

  def process_request(self,request,spider):
    request.headers['User-Agent'] = self.user_agent.random

編寫pipelines.py,將資料存為json檔案

import json

class LagouPipeline:
  def process_item(self,item,spider):
    with open('jobs.json','a',encoding='utf-8') as f:
      item_json = json.dumps(dict(item),ensure_ascii=False,indent=2)
      f.write(item_json)
      f.write('\n')

編寫settings.py

# 設定日誌顯示
LOG_LEVEL = 'WARNING'

# 設定ROBOTSTXT協議,若為true則不能爬取資料
ROBOTSTXT_OBEY = False

# 設定下載器延遲,反爬蟲的一種策略
DOWNLOAD_DELAY = 0.25

# 開啟DOWNLOADER_MIDDLEWARES
DOWNLOADER_MIDDLEWARES = {
 # 'LaGou.middlewares.LagouDownloaderMiddleware': 543,'LaGou.middlewares.RandomUserAgentDM' :100,}

# 開啟ITEM_PIPELINES
ITEM_PIPELINES = {
 'LaGou.pipelines.LagouPipeline': 300,}

啟動爬蟲

scrapy crawl lagou

發現依然只能5 6頁,說明拉勾網的反爬確實做得比較好,還可以繼續通過使用代理來進行反反爬,這裡就不再演示了,

Python爬蟲例項——scrapy框架爬取拉勾網招聘資訊

檢視爬取結果

Python爬蟲例項——scrapy框架爬取拉勾網招聘資訊

以上就是Python爬蟲例項——scrapy框架爬取拉勾網招聘資訊的詳細內容,更多關於Python爬蟲爬取招聘資訊的資料請關注我們其它相關文章!