使用scrapy+IP代理+多线程爬虫对拉钩网在杭州互联网职位信息的抓取

这是本人第一次写博客,难免会有很多写的不妥的地方,欢迎大家给予指导,来互相 学习!本篇博客涉及的代码在本人的github上【地址】,欢迎随时访问!本人写这个爬虫程序的初衷是想分析一下拉钩网上杭州互联网职位的一些状况,主要从一下几个指标进行的分析 --职位数量,平均薪资,不同学历的平均薪资,实习/全职的平均薪资,不同工作经验的平均薪资。这个文章主要分析爬虫的过程,想看具体分析结果,本人会最快更新博客,贴上本人的分析BI。废话不多说,接下来分享一下爬虫过程。本次爬虫分为两步,第一步先爬取每一个职位详细页面的URL,第二步根据这个URL访问详细页面进行相关信息抓取

1、详细页面URL的抓取

1)如下图,拉钩首页有很多职位,本人确定爬取的职位有14个,这个可以通过f12来分析自己想爬取的职位

2)从上图中的点击java可以得到下图信息,从下图页面就可以抓取详细页面的URL

3)然后使用的是scrapy技术对url进行的爬取

首先是爬虫部分,在本人项目的myspider文件中

# -*- coding: utf-8 -*-
import scrapy
from scrapy.selector import Selector
import re
from scrapy.http import Request
from LagouProject.items import LagouprojectItem
from scrapy.conf import settings

class MyspiderSpider(scrapy.spiders.Spider):
    name = 'myspider'
    allowed_domains = ['www.lagou.com']
    start_urls = ['https://www.lagou.com/']
    cookie = settings['COOKIE']  # 带着Cookie向网页发请求
    def parse(self, response):
        se=Selector(response) #创建查询对象

        if(re.match("https://www.lagou.com/", response.url)):#如果url能够匹配到需要爬取的url就爬取  [starts-with(@href, 'https://www.lagou.com/zhaopin/')]
            src = se.xpath("//div[@class='mainNavs']/div[1]//div[@class='category-list']/a")
            for i in range(1,len(src)+1):
                name = se.xpath("//div[@class='mainNavs']/div[1]//div[@class='category-list']/a[%d]/text()"%i).extract()#获取a标签中的内容
                url = se.xpath("//div[@class='mainNavs']/div[1]//div[@class='category-list']/a[%d]/@href"%i).extract()#获取a标签中的href属性地址
                if name and url:
                     yield Request(url=url[0], meta={'name': name[0]}, callback=self.parse_url ,cookies=self.cookie)



    def parse_url(self,response):          #该函数是用来获取每一个招聘公司的url和招聘的职位
        name = response.meta['name']
        cookie = response.request.headers.getlist('Cookie')
        print '-----------------------------------------------cookie'
        print cookie
        # print response.body
        se = Selector(response)
        if(re.match("https://www.lagou.com/", response.url)):
            src = se.xpath("//div[@id='s_position_list' ]/ul/li")
            for i in range(1,len(src)+1):
                url = se.xpath("//div[@id='s_position_list' ]/ul/li[%d]//a[@class='position_link']/@href"%i).extract()#获取个招聘信息的详细页面的url
                item = LagouprojectItem()
                item['name'] = name
                item['url'] = url[0]
                yield  item
            nextpage = se.xpath("//div[@class='pager_container']/a[last()]/@href").extract()#获取下一页的地址
            print nextpage
            if (re.match(r"https://www.lagou.com/zhaopin/\w+/\d+/",nextpage[0])):  #判断是否为最后一页
                yield Request(url=nextpage[0], meta={'name': name}, callback=self.parse_url)
                response.request.headers.getlist('Cookie')



setting文件配置部分

#控制并发
CONCURRENT_REQUESTS_PER_DOMAIN = 1  #使爬虫同时只能对每个域名发起一个请求
DOWNLOAD_DELAY =3   #每两次请求之间存在延迟时间为5秒
#Mysql数据库的配置信息
MYSQL_HOST = '127.0.0.1'
MYSQL_DBNAME = 'lagou'         #数据库名字,请修改
MYSQL_USER = 'root'             #数据库账号,请修改
MYSQL_PASSWD = '123456'         #数据库密码,请修改

MYSQL_PORT = 3306               #数据库端口,在dbhelper中使用
BOT_NAME = 'LagouProject'

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

DEFAULT_REQUEST_HEADERS = {
  'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
  'Accept-Language':'zh-CN,zh;q=0.8',
  'Host':'www.lagou.com',
  'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.113 Safari/537.36',
  'Connection':'keep-alive'
}
# 使用transCookie.py翻译出的Cookie字典
COOKIE = {你的cookie信息}

在setting文件中大家主要修改自己数据库配置控制并发量(需要自己添加)、以及把DEFAULT_REQUEST_HEADERS开启,这个代表请求的默认消息头,类似大家使用urllib2中设headers。最后大家添加上COOKIE(需要自己添加)信息,这个COOKIE会在爬虫逻辑代码中使用到,阅读上面爬虫代码可以观察到,以及在第二部分的多线程,IP代理爬虫也会使用到。本人尝试了一下,不加如果不加COOKIE访问会出现错误。大家可以在浏览器中f12获取到cookie的相信,不知道的大家可以自行百度一下,但是不能直接使用。接下来贴出transCookie转换的代码

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

class transCookie:
    def __init__(self, cookie):
        self.cookie = cookie

    def stringToDict(self):
        '''
        将从浏览器上Copy来的cookie字符串转化为Scrapy能使用的Dict
        :return:
        '''
        itemDict = {}
        items = self.cookie.split(';')
        for item in items:
            key = item.split('=')[0].replace(' ', '')
            value = item.split('=')[1]
            itemDict[key] = value
        return itemDict

if __name__ == "__main__":
    cookie = '你的cookie信息'
    trans = transCookie(cookie)
    print trans.stringToDict()

接下来是数据文件pipeline的代码,这个文件主要是写数据库的相关信息

# -*- coding: utf-8 -*-
from twisted.enterprise import adbapi
import MySQLdb
import MySQLdb.cursors

class LagouprojectPipeline(object):
       # 保存到数据库中对应的class
       # 1、在settings.py文件中配置
       # 2、在自己实现的爬虫类中yield item,会自动执行

    def __init__(self,dbpool):
        self.dbpool=dbpool
        # 这里注释中采用写死在代码中的方式连接线程池,可以从settings配置文件中读取,更加灵活
        #     self.dbpool=adbapi.ConnectionPool('MySQLdb',
        #                                   host='127.0.0.1',
        #                                   db='lagou',
        #                                   user='root',
        #                                   passwd='123456',
        #                                   cursorclass=MySQLdb.cursors.DictCursor,
        #                                   charset='utf8',
        #                                   use_unicode=False)

    @classmethod
    def from_settings(cls,settings):
        '''1、@classmethod声明一个类方法,而对于平常我们见到的则叫做实例方法。
           2、类方法的第一个参数cls(class的缩写,指这个类本身),而实例方法的第一个参数是self,表示该类的一个实例
           3、可以通过类来调用,就像C.f(),相当于java中的静态方法'''
        dbparams=dict(
            host=settings['MYSQL_HOST'],#读取settings中的配置
            db=settings['MYSQL_DBNAME'],
            user=settings['MYSQL_USER'],
            passwd=settings['MYSQL_PASSWD'],
            charset='utf8',#编码要加上,否则可能出现中文乱码问题
            cursorclass=MySQLdb.cursors.DictCursor,
            use_unicode=False,
        )
        dbpool=adbapi.ConnectionPool('MySQLdb',**dbparams)#**表示将字典扩展为关键字参数,相当于host=xxx,db=yyy....
        return cls(dbpool)#相当于dbpool付给了这个类,self中可以得到

    #pipeline默认调用
    def process_item(self, item, spider):
        query=self.dbpool.runInteraction(self._conditional_insert,item)#调用插入的方法
        query.addErrback(self._handle_error,item,spider)#调用异常处理方法
        return item

    #写入数据库中
    def _conditional_insert(self,tx,item):
        #print item['name']
        sql="insert into lgadress(name,url) values(%s,%s)"

        params=(item["name"],item["url"])
        tx.execute(sql,params)

    #错误处理方法
    def _handle_error(self, failue, item, spider):
        print '--------------database operation exception!!-----------------'
        print '-------------------------------------------------------------'
        print failue

是item文件的配置,这个不做过多阐述,类似于orm

import scrapy


class LagouprojectItem(scrapy.Item):
      name = scrapy.Field()
      url = scrapy.Field()

最后爬取的效果给大家展示一下

2、详细页面信息的获取

1)通过第一步获取的详细页面的URL进行访问得到详细页面,一共抓取的维度信息是java开发工程师下面一行的5个信息。
2)接下来附上本人的代码,这部分代码比较长,为了方便大家阅读,注释写的很清晰,如果不是很明白可以留言私信我。爬虫逻辑代码spiderLG
#encoding=utf8
import urllib2
from bs4 import BeautifulSoup
import socket
import urllib
import requests
import random
from LagouProject.dbhelper import TestDBHelper
import threading
import time
import re
from lxml import etree
from scrapy.conf import settings
import sys
reload(sys)
sys.setdefaultencoding('utf-8')

#设置header
User_Agent = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.113 Safari/537.36'
header = {}
header['User-Agent'] = User_Agent

'''
获取西刺首页的所有代理IP地址
'''
def getProxyIp():
     proxy = []
     for i in range(1,2):
          try:
               url = 'http://www.xicidaili.com/nn/'+str(i)
               req = urllib2.Request(url,headers=header)
               res = urllib2.urlopen(req).read()
               soup = BeautifulSoup(res,'html.parser',from_encoding='utf8')
               ips = soup.findAll('tr')
               for x in range(1,len(ips)):
                ip = ips[x]
                tds = ip.findAll("td")
                ip_temp = tds[1].contents[0]+"\t"+tds[2].contents[0]
                proxy.append(ip_temp)
          except:
                continue
     return proxy

'''
验证获得的代理IP地址是否可用
'''

def validateIp(proxy):
      url = "http://ip.chinaz.com/getip.aspx"
      available_ip =[]
      socket.setdefaulttimeout(3)
      for i in range(0,len(proxy)):
          try:
               ip = proxy[i].strip().split("\t")
               proxy_host = "http://"+ip[0]+":"+ip[1]
               proxy_temp = {"http":proxy_host}
               res = urllib.urlopen(url,proxies=proxy_temp).read()
               available_ip.append(proxy_host)
               #print proxy[i]
          except Exception,e:
               continue
      return available_ip

def spider(validateProxy,max_threads=2):
     dbhelper = TestDBHelper()
     #调用TestDBHelper中的testSelect方法读取数据库中的URL
     results = dbhelper.testSelect()
     rLock = threading.RLock()  #RLock对象
     s  = requests.session()
     #需要抓取的URL列表
     url_queue=[]
     #职位列表
     name_list=[]
     #访问失败URL列表
     fail_url=[]
     fail_name=[]
     for row in results:
         name = row[1]
         url = row[2]
         name_list.append(name)
         url_queue.append(url)
     #设置代理
     def process_queue():
          #随机选取一个IP代理
          IP = random.choice(validateProxy)
          while True:
              try:
                  rLock.acquire()  #获取锁
                  url = url_queue.pop()
                  name = name_list.pop()
                  rLock.release()   #释放锁
                  #sleep_time = (random.choice(num_list)%3)*10 #设置随机睡眠时间
                  time.sleep(5)
                  print  'sub thread start!the thread name is:%s\r' % threading.currentThread().getName()
              except:
                  #判断url_queue是否为空
                  rLock.release()
                  break
              try:
                  #设置代理
                  proxies = {
                      'http' :  IP,
                  }
                  print IP
                  cookie = settings['COOKIE']  # 带着Cookie向网页发请求
                  #print cookie
                  #将字典转为CookieJar:
                  cookies = requests.utils.cookiejar_from_dict(cookie, cookiejar=None, overwrite=True)
                  s.cookies= cookies
                  html = s.get(url,headers=header, timeout=10,proxies=proxies,).content.encode('utf-8')
                  #print html
                  page = etree.HTML(html.decode('utf-8'))
                  #print page
                  elements = page.xpath("//div[@class='position-content-l']/dd//span")
                  #print len(elements)
                  #break

                  #一条数据库记录的信息存放在这个列表中
                  content_list=[]
                  for element in elements:
                      content = element.text
                      #print content
                      if content:
                          content = content.replace('/','')
                          if 'k' in content or '年' in content or 'K' in content:
                                 list = re.findall(r'\d+',content)
                                 value = [float(i) for i in list]
                                 content = sum(value)/len(value)
                      else:
                          content = 'null'
                      content_list.append(content)

                  content_list.append(name)
                  #print content_list
                  #将记录插入数据库
                  dbhelper.testInsert(content_list)

              except Exception,e:
                  print '---------------------------------------异常'
                  print url
                  print e
                  IP = random.choice(validateProxy) #如果在timeout内没有访问网页成功,从新选择一个代理
                  rLock.acquire()  #获取锁
                  fail_name.append(name)
                  fail_url.append(url)  #把访问失败的网页添加到fail_url以备递归访问
                  rLock.release()  #释放锁
                  continue
     #设置多线程
     threads=[]
     while threads or url_queue:
          for thread in threads:
              if not thread.is_alive():
                 #移除the stopped threads
                 threads.remove(thread)
          while len(threads) < max_threads and url_queue:
              time.sleep(5)
              #can start some more threads
              thread = threading.Thread(target=process_queue)
              # set daemon so main thread can exit when receives ctrl-c
              thread.setDaemon(True)
              print '---------------------------------------------------------------------------------多线程'+thread.name

              thread.start()
              threads.append(thread)
          time.sleep(1)


     if fail_url:   #把访问失败的URL递归调用spider方法
         failspider(fail_url,fail_name,validateProxy,max_threads=1)



def failspider(url_list,name_list,proxy,max_threads=2):
      print url_list
      print name_list
      dbhelper = TestDBHelper()
      s  = requests.session()
      rLock = threading.RLock()  #RLock对象
      #访问失败URL列表
      fail_url=[]
      fail_name=[]
      def process_queue():
          #随机选取一个IP代理
          IP = [random.choice(proxy)]
          while True:
              try:
                  rLock.acquire()   #获取锁
                  url = url_list.pop()
                  name = name_list.pop()
                  rLock.release()   #释放锁
                  #sleep_time = (random.choice(num_list)%3)*10
                  time.sleep(5)
                  print  'sub thread start!the thread name is:%s\r' % threading.currentThread().getName()
              except:
                  #判断url_queue是否为空
                  rLock.release()
                  break
              try:
                  #设置代理
                  proxies = {
                      'http' :  IP,
                  }
                  print IP
                  cookie = settings['COOKIE']  # 带着Cookie向网页发请求
                  #print cookie
                  #将字典转为CookieJar:
                  cookies = requests.utils.cookiejar_from_dict(cookie, cookiejar=None, overwrite=True)
                  s.cookies= cookies
                  html = s.get(url,headers=header, timeout=10,proxies=proxies).content.encode('utf-8')
                  #print html
                  page = etree.HTML(html.decode('utf-8'))
                  #print page
                  elements = page.xpath("//div[@class='position-content-l']/dd//span")
                  #break
                  #一条数据库记录的信息存放在这个列表中
                  content_list=[]
                  for element in elements:
                      content = element.text
                      if content:
                          content = content.replace('/','')
                          if 'k' in content or '年' in content or 'K' in content:
                                 list = re.findall(r'\d+',content)
                                 value = [float(i) for i in list]
                                 content = sum(value)/len(value)
                      else:
                          content = 'null'
                      content_list.append(content)

                  content_list.append(name)
                  #print content_list
                  #将记录插入数据库
                  dbhelper.testInsert(content_list)

              except Exception,e:
                  print '---------------------------------------异常'
                  print e
                  IP = random.choice(validateProxy) #如果在timeout内没有访问网页成功,从新选择一个代理
                  rLock.acquire()   #获取锁
                  fail_url.append(name)
                  fail_name.append(url)  #把访问失败的网页添加到fail_url以备递归访问
                  rLock.release()   #释放锁
                  continue
      #设置多线程
      threads=[]
      while threads or url_list:
          for thread in threads:
              if not thread.is_alive():
                 #移除the stopped threads
                 threads.remove(thread)
          while len(threads) < max_threads and url_list:
              time.sleep(5)
              #can start some more threads
              thread = threading.Thread(target=process_queue)
              # set daemon so main thread can exit when receives ctrl-c
              thread.setDaemon(True)
              print '---------------------------------------------------------------------------------多线程'+thread.name
              thread.start()
              threads.append(thread)
          time.sleep(1)


      if fail_url:   #把访问失败的URL递归调用spider方法
          failspider(fail_url,fail_name,proxy,max_threads=1)#递归调用failspider方法




if __name__ == '__main__':
 # proxy = getProxyIp()
 # validateProxy = validateIp(proxy)
 # print validateProxy
 validateProxy=[u'http://60.209.166.172:8118', u'http://121.43.227.212:808', u'http://113.87.90.218:53281', u'http://112.123.42.94:9745', u'http://175.42.102.252:8118', u'http://116.248.172.233:80', u'http://175.16.221.31:8118', u'http://171.36.182.180:8118', u'http://115.215.50.218:8118', u'http://171.126.12.9:80', u'http://113.205.0.23:8118', u'http://106.58.152.171:80', u'http://59.63.178.203:53281', u'http://111.155.116.239:8123', u'http://117.90.34.87:8118', u'http://111.155.116.200:8123', u'http://61.183.176.122:53281', u'http://112.114.96.94:8118', u'http://58.49.122.30:53281', u'http://112.114.94.8:8118', u'http://27.22.63.12:808', u'http://112.114.78.28:8118']

 spider(validateProxy,max_threads=2)
要使用到的数据库代码dbhelper, 在dbhelper是链接数据库和对数据库进行建库建表,以及增删改查操作的工具类,在爬虫代码中有使用到,大家注意观察
# -*- coding: utf-8 -*-
import MySQLdb
from scrapy.utils.project import get_project_settings #导入seetings配置
import sys
reload(sys)
sys.setdefaultencoding('utf-8')

class DBHelper():

    '''这个类也是读取settings中的配置,自行修改代码进行操作'''
    def __init__(self):
        self.settings=get_project_settings() #获取settings配置,设置需要的信息

        self.host=self.settings['MYSQL_HOST']
        self.port=self.settings['MYSQL_PORT']
        self.user=self.settings['MYSQL_USER']
        self.passwd=self.settings['MYSQL_PASSWD']
        self.db=self.settings['MYSQL_DBNAME']

    #连接到mysql,不是连接到具体的数据库
    def connectMysql(self):
        conn=MySQLdb.connect(host=self.host,
                             port=self.port,
                             user=self.user,
                             passwd=self.passwd,
                             #db=self.db,不指定数据库名
                             charset='utf8') #要指定编码,否则中文可能乱码
        return conn
    #连接到具体的数据库(settings中设置的MYSQL_DBNAME)
    def connectDatabase(self):
        conn=MySQLdb.connect(host=self.host,
                             port=self.port,
                             user=self.user,
                             passwd=self.passwd,
                             db=self.db,
                             charset='utf8') #要指定编码,否则中文可能乱码
        return conn

    #创建数据库
    def createDatabase(self):
        '''因为创建数据库直接修改settings中的配置MYSQL_DBNAME即可,所以就不要传sql语句了'''
        conn=self.connectMysql()#连接数据库

        sql="create database if not exists "+self.db
        cur=conn.cursor()
        cur.execute(sql)#执行sql语句
        cur.close()
        conn.close()

    #创建表
    def createTable(self,sql):
        conn=self.connectDatabase()

        cur=conn.cursor()
        cur.execute(sql)
        cur.close()
        conn.close()
    #查询数据
    def select(self,sql):
        conn = self.connectDatabase()
        cur = conn.cursor()
        cur.execute(sql)
        # 获取所有记录
        results = cur.fetchall()
        cur.close()
        conn.close()
        return results
    #插入数据
    def insert(self,sql,*params):#注意这里params要加*,因为传递过来的是元组,*表示参数个数不定
        conn=self.connectDatabase()

        cur=conn.cursor();
        cur.execute(sql,params)
        conn.commit()#注意要commit
        cur.close()
        conn.close()
    #更新数据
    def update(self,sql,*params):
        conn=self.connectDatabase()

        cur=conn.cursor()
        cur.execute(sql,params)
        conn.commit()#注意要commit
        cur.close()
        conn.close()

    #删除数据
    def delete(self,sql,*params):
        conn=self.connectDatabase()

        cur=conn.cursor()
        cur.execute(sql,params)
        conn.commit()
        cur.close()
        conn.close()



'''测试DBHelper的类'''
class TestDBHelper():
    def __init__(self):
        self.dbHelper=DBHelper()

    #测试创建数据库(settings配置文件中的MYSQL_DBNAME,直接修改settings配置文件即可)
    def testCreateDatebase(self):
        self.dbHelper.createDatabase()
    #测试创建表
    def testCreateTable(self):
        sql="create table testtable(id int primary key auto_increment,name varchar(50),url varchar(200))"
        self.dbHelper.createTable(sql)
    def testSelect(self):
        sql="select  * from lgadress"
        results = self.dbHelper.select(sql)
        return results
    #测试插入
    def testInsert(self,params):

            sql="insert into lgdetails(salary,city,jobexp,degree,jobtype,jobname) values(%s,%s,%s,%s,%s,%s)"

            params=(params[0],params[1],params[2],params[3],params[4],params[5])

            #params=("test","test")
            self.dbHelper.insert(sql,*params) #  *表示拆分元组,调用insert(*params)会重组成元组


    def testUpdate(self):
        sql="update testtable set name=%s,url=%s where id=%s"
        params=("update","update","1")
        self.dbHelper.update(sql,*params)

    def testDelete(self):
        sql="delete from testtable where id=%s"
        params=("1")
        self.dbHelper.delete(sql,*params)

if __name__=="__main__":
    testDBHelper=TestDBHelper()
    #testDBHelper.testCreateDatebase()  #执行测试创建数据库
    #testDBHelper.testCreateTable()     #执行测试创建表
    testDBHelper.testInsert()          #执行测试插入数据
    #testDBHelper.testUpdate()          #执行测试更新数据
    #testDBHelper.testDelete()          #执行测试删除数据

最后大家展示一下爬取的结果

以上就是本人全部爬取过程,在这个过程很多东西也是自己第一次接触,借鉴了很多网友爬虫过程,该爬虫如果涉及到您的利益,请与本人联系,本人以最快速度删除文章内容

 

  • 12
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值