Python爬虫:全国大学招生信息(一):爬取数据 (多进程、多线程、代理)

news/2024/7/19 9:53:14 标签: python, 爬虫

无聊爬爬,仅供学习,无其他用途

这几天在高考派(http://www.gaokaopai.com/)上爬招生信息,其中也绕了不少弯路也学到了许多。

以下为涉及到的模块

python">import requests
from fake_useragent import UserAgent
from multiprocessing import Process
import urllib.request as r
import threading
import re
import time
import random

首先看我们需要爬取的网页

不同学校对应文科理科以及全国各省才能确定招生计划,通过点击搜索可以获取到一个请求页面,是通过ajax实现的

其中发送的数据如下

通过多个页面对比可以知道id指的是学校id,tpye为1或2指的是文科或理科,city自然是城市号,state为1或0表示有或无招生计划。

所以我们需要先获取全部id和city并写入到txt中,这部分通过在首页中使用BeautifulSoup和正则来实现,具体代码很简单,得到学校和对应的url、省份与id号的文本文件:

一共有3054个大学

以及31个省份

然后通过进入各个大学的url中发送请求获取json数据

python">#获取id列表
def getSchoolIdList():
#...

#获取city列表
def getCityIdList():
# ...

#获取请求数据列表
def getDataList(schoolId,cityId):
    dataList = []
    for sid in schoolId:
        for cid in cityId:
            for type in [1, 2]:
                dataList.append('id={}&type={}&city={}&state=1'.format(sid, type, cid))
    return dataList

也就是说一共有3054x31x2=189348个数据需要获取

并且考虑到是大量数据,为了防止被封还得设置代理以及不同的消息头

我是通过向代理网站发送请求获取代理ip(19块一天,不过能无限取),另外在proxies字典中如果是'http'会报错,'http'和'http'都有也有错误,不知为啥,索性从代理网站获取https的ip

python">#获取代理ip列表,其中有15个ip
def getProxyList():
    url1 = 'http://api3.xiguadaili.com/ip/?tid=558070598680507&num=15&delay=1&category=2&protocol=https'
    res = requests.get(url1)
    proxyList = []
    for ip in res.text.split('\r\n'):
        proxyList.append(ip)
    return proxyList

#随机获取一条代理ip
def getProxies(proxyList):
    proxy_ip = random.choice(proxyList)
    proxies = {
        'https': 'https://' + proxy_ip
    }
    return proxies

#随机获取一个消息头
def getUserAgent():
    h = {
        'User-Agent': UserAgent().random,
        'X-Requested-With': 'XMLHttpRequest'
    }
    return h

然后就是发送请求了,我是封装在一个线程类中(虽然python的线程比较鸡肋,就纯当练练手)

python">class SpiderThread(threading.Thread):
    def __init__(self,begin,end,name,dataList):
        super().__init__()
        self.begin=begin
        self.end=end
        self.name=name
        self.dataList=dataList
    def run(self):
        f3 = open('D:/PyGaokaopai/majorinfo2.txt', 'a', encoding='utf-8')
        url = 'http://www.gaokaopai.com/university-ajaxGetMajor.html'
        flag=0
        num=0
        n=0
        i=0

        h = getUserAgent()
        proxyList = getProxyList()

        for i in range(int(self.begin), int(self.end)):
            flag+=1
            n+=1
            i+=1
            num+=1
            data = self.dataList[i].encode()

            #每获取5个数据更换一次消息头和代理ip
            if(flag == 5):
                h = getUserAgent()
                proxies = getProxies(proxyList)
                flag = 0
                time.sleep(0.4)

            #每获取50个数据等两秒更换一次代理ip列表
            if(i==50):
                time.sleep(2)
                proxyList = getProxyList()
                i=0
            
            #发送请求数据,并且获取数据写入txt
            write2txt(url,data,h,proxyList,f3,num,self.name)

接下来就是这个write2txt的方法了,由于不能保证ip质量,所以我们应该在获取数据的时候设置一个timeout,超时则更换一个ip发送请求;其次通过测试如果网站在同时间获取到大量请求会获取一个错误的页面的代码,这时候获取的数据就有误了。我第一次爬的时候就获取到了大量错误页面的代码

大概到第2000多次就错误了,所以当获取错误数据时应该等一会再获取

所以为了防止以上两个错误发生,这个write2txt的方法应该有递归,而且是两个

python">def write2txt(url,data,h,proxyList,f3,num,name):
    try:
        proxies = getProxies(proxyList)
        httpproxy_handler = r.ProxyHandler(proxies)
        opener = r.build_opener(httpproxy_handler)
        req = r.Request(url, data=data, headers=h)
        data1 = opener.open(req, timeout=2).read().decode('utf-8', 'ignore')
        
        #通过观察,错误的数据是错误页面的html代码,等待3秒再递归
        if(str(data1).startswith('<!DOCTYPE html>')):
            time.sleep(3)
            write2txt(url,data,h,proxyList,f3,num,name)
            data1=''
        f3.write(data1 + '\r\n')
        print(str(num),name)
    
    #超时则递归
    except Exception as e:
        time.sleep(1)
        write2txt(url, data, h, proxyList, f3, num,name)

因为在操作错误代码的逻辑中把获取到的data设置为空字符串了,所以还是会写入换行符,所以这个文本文件之后还是要操作一下

最后,提高效率通过使用多进程来实现

python">def getThread(i,name,dataList):
    t1=SpiderThread(str(int(i)*3054),str(int(i)*3054+1527),name+'.t1',dataList)
    t2 = SpiderThread(str(int(i)*3054+1527), str((int(i)+1)*3054), name+'.t2', dataList)
    t1.start()
    time.sleep(1.5)
    t2.start()

if __name__ == '__main__':
    schoolId = getSchoolIdList()
    cityId = getCityIdList()
    dataList = getDataList(schoolId,cityId)
    for i in range(62):
        p = Process(target=getThread, args=(i,'P'+str(i),dataList))
        p.start()
        time.sleep(1.5)
    p.join()

我的想法是一共62个进程,每个进程分为2个线程,每个线程发送1527个请求

其中有state为0 的数据,也就是没有招生计划,程序大概跑了两小时

并且由于错误页面那块的逻辑,会有空行:

所以这个文本还是要通过正则来处理一下

python">import re
f=open('D:/PyGaokaopai/majorinfo2.txt','r',encoding='utf-8')

list = re.findall('\{\"data.*?\"status\":1\}',f.read())
f1=open('D:/PyGaokaopai/majorinfo.txt','w',encoding='utf-8')

for i in range(len(list)):
    f1.write(list[i]+'\n')

f.close()
f1.close()

最后得到一个 243M 的文本文件,一共有 61553个有效数据

然后就是数据处理了:D

 

完整代码(除去获取id,city文本代码以及有效数据处理)

python">import requests
from fake_useragent import UserAgent
import re
import urllib.request as r
import threading
import time
import random
from multiprocessing import Process

#获取id列表
def getSchoolIdList():
    f1 = open('schoolurl.txt', 'r', encoding='utf-8')
    schoolId = []
    while True:
        try:
            line = f1.readline()
            url = line.split(',')[1]
            id = re.search('\d+', url).group()
            schoolId.append(id)
        except Exception as e:
            pass
        if (not line):
            break
    f1.close()
    return schoolId
#获取city列表
def getCityIdList():
    f2 = open('citynumber.txt', 'r', encoding='utf-8')
    cityId = []
    while True:
        try:
            line = f2.readline()
            url = line.split(',')[1]
            id = re.search('\d+', url).group()
            cityId.append(id)
        except Exception as e:
            pass
        if (not line):
            break
    f2.close()
    return cityId

#获取请求列表
def getDataList(schoolId,cityId):
    dataList = []
    for sid in schoolId:
        for cid in cityId:
            for type in [1, 2]:
                dataList.append('id={}&type={}&city={}&state=1'.format(sid, type, cid))
    return dataList

#获取代理ip列表
def getProxyList():
    url1 = 'http://api3.xiguadaili.com/ip/?tid=558070598680507&num=15&delay=1&category=2&protocol=https'
    res = requests.get(url1)
    proxyList = []
    for ip in res.text.split('\r\n'):
        proxyList.append(ip)
    return proxyList

#随机获取一条代理ip
def getProxies(proxyList):
    proxy_ip = random.choice(proxyList)
    proxies = {
        'https': 'https://' + proxy_ip
    }
    return proxies

#随机获取一个消息头
def getUserAgent():
    h = {
        'User-Agent': UserAgent().random,
        'X-Requested-With': 'XMLHttpRequest'
    }
    return h


class SpiderThread(threading.Thread):
    def __init__(self,begin,end,name,dataList):
        super().__init__()
        self.begin=begin
        self.end=end
        self.name=name
        self.dataList=dataList
    def run(self):
        f3 = open('D:/PyGaokaopai/majorinfo2.txt', 'a', encoding='utf-8')
        url = 'http://www.gaokaopai.com/university-ajaxGetMajor.html'
        flag=0
        num=0
        n=0
        i=0

        h = getUserAgent()
        proxyList = getProxyList()

        for i in range(int(self.begin), int(self.end)):
            flag+=1
            n+=1
            i+=1
            num+=1
            data = self.dataList[i].encode()
            if(flag == 5):
                h = getUserAgent()
                proxies = getProxies(proxyList)
                flag = 0
                time.sleep(0.4)

            if(i==50):
                time.sleep(2)
                proxyList = getProxyList()
                i=0

            write2txt(url,data,h,proxyList,f3,num,self.name)

        print(self.name+' finished-----------------------')


def write2txt(url,data,h,proxyList,f3,num,name):
    try:
        proxies = getProxies(proxyList)
        httpproxy_handler = r.ProxyHandler(proxies)
        opener = r.build_opener(httpproxy_handler)
        req = r.Request(url, data=data, headers=h)
        data1 = opener.open(req, timeout=2).read().decode('utf-8', 'ignore')
        if(str(data1).startswith('<!DOCTYPE html>')):
            time.sleep(3)
            write2txt(url,data,h,proxyList,f3,num,name)
            data1=''
        f3.write(data1 + '\r\n')
        print(str(num),name)
    except Exception as e:
        time.sleep(1)
        write2txt(url, data, h, proxyList, f3, num,name)



def getThread(i,name,dataList):
    t1=SpiderThread(str(int(i)*3054),str(int(i)*3054+1527),name+'.t1',dataList)
    t2 = SpiderThread(str(int(i)*3054+1527), str((int(i)+1)*3054), name+'.t2', dataList)
    t1.start()
    time.sleep(1.5)
    t2.start()

if __name__ == '__main__':
    schoolId = getSchoolIdList()
    cityId = getCityIdList()
    dataList = getDataList(schoolId,cityId)
    for i in range(62):
        p = Process(target=getThread, args=(i,'P'+str(i),dataList))
        p.start()
        time.sleep(1.5)
    p.join()

 


http://www.niftyadmin.cn/n/1033155.html

相关文章

Python爬虫:全国大学招生信息(二):生源数据分析(matplotlib)

在上一篇博客&#xff08;https://blog.csdn.net/qq_39192827/article/details/87136836&#xff09;中爬取了6W条json数据&#xff0c;接下来通过2D可视化来分析这些数据。需要使用的是matplotlib模块。 之前我们获取了一个大学名字对应url的txt&#xff0c;通过大学名字去6w…

Python爬虫:深度、广度(多线程)爬取网页链接并控制层级

在使用爬虫爬取多个页面时&#xff08;比如爬取邮箱&#xff0c;手机号等&#xff09;&#xff0c;一般层级越高与我们原始目标数据之间准确率越低&#xff0c;所以很有必要控制爬取层级达到有效爬取 无论是深度还是广度爬取&#xff0c;都需要以下变量和方法 #链接的正则表达…

Python:第六次全国人口普查数据分析及可视化(pandas、matplotlib)

一、数据获取 在国家统计局网中下载第六次人口普通统计表&#xff1a;http://www.stats.gov.cn/tjsj/pcsj/rkpc/6rp/indexch.htm 然后通过pandas将excel数据解析为多级字典 先观察excel数据 可以转化为这样的多级词典&#xff1a; 理清字典关系后代码就简单了 def getDataD…

八大排序算法浅析(Java)

目录 一、选择排序 1、直接选择排序 2、堆排序 二、交换排序 1、冒泡排序 2、快速排序 三、插入排序 1、直接插入排序 2、希尔排序 三、归并排序 四、基数排序 附&#xff1a;八大排序算法比较 关于Java中的排序算法&#xff0c;此篇讨论的都是内部排序&#xff0c…

Java并发编程(二):多线程交互(案例)

目录 线程状态 线程间交互 线程间的排队&#xff1a;join() wait()、notify()、notifyAll() 死锁 信号量 Semaphore CountDownLatch 线程状态 在jdk1.8中&#xff0c;线程可以处于以下状态之一&#xff1a; NEW &#xff1a;尚未启动的线程处于此状态。 RUNNABLE &am…

Java并发编程(三):多线程安全分析(案例)

目录 模拟数组库 Vector与ArrayList 源码对比 Vector ArrayList synchronized Collections工具类 Lock的读锁和写锁 ThreadLocal 模拟数组库 数据库就类似于一个集合&#xff0c;element其实就是相当于数据库中的一行记录。就比如有一个图书表&#xff0c;有人会去查阅…

Linux系统:shell(一):概述、bash、变量、环境配置

目录 shell概述 /bin/bash的功能 shell中的变量 1、命名规则 2、使用规则 3、只读变量和删除变量 4、环境变量 特殊的环境变量 环境变量与自定义变量的区别 5、键盘录入变量值 shell环境配置 login shell non-login shell shell概述 shell本意为"壳"&…

Linux系统:shell(二):数据流重定向

数据流重定向就是将命令执行后产生的数据从默认显示位置重定向到其他位置&#xff0c;比如重定向到文件或者是设备 数据流重定向可以分成以下三类 类型默认位置代码符号标准输出&#xff08;stdout&#xff09;屏幕1 > (1>) or >> or(1>>) 标准错误输出…