python 爬虫 4 (实例:世界大学排名列表、手写分布式爬虫)

写在前面

一个简单的实例爬取世界大学排名列表:Xpath的使用、多线程、reids

关于lxml,python3.7过后,lxml与之前的使用方法略有不同

使用前先安装lxml包:pip install lxml

python2

from lxml import html

r = requests.get(网址)
r_html = etree.HTML(r.text)
print(r_html.xpath(xpath语句))

python3及之后版本:

from lxml import html

r = requests.get(网址)
e = html.etree
r_html = e.HTML(r.text)
print(r_html.xpath(xpath语句))

1、xpath helper

下载谷歌浏览器的插件xpath helper

百度网盘:pan.baidu.com/s/1phXPKllX0-BA7IDxPGRhZA

密码:yuuv

下载完成更改文件名如下
在这里插入图片描述
解压,然后进入谷歌浏览器-更多工具-扩展程序
在这里插入图片描述
找到解压目录
在这里插入图片描述
在这里插入图片描述
按Ctrl+shift+x可以调出来使用
在这里插入图片描述

2、爬迁木网世界大学排名列表

迁木网世界大学排名列表

http://www.qianmu.org/ranking/1528.htm

2.1、分析网页

先查看网站源码,我们发现世界排名列表在class="rankItem"的标签里面哪个
我们现在需要这个列表里所有大学的的详情页连接:提取class="rankItem"里的所有连接

//div[@class="rankItem"]//a/@href

在这里插入图片描述
在这里插入图片描述
通过分析发现这些连接里有的不是大学详情页的连接,所以我们精确到td[2]

//div[@class='rankItem']//tr/td[2]//a/@href

在这里插入图片描述

找到这些连接后,复制第一个连接进去,看看详情页的内容
在这里插入图片描述

http://www.qianmu.org/%E9%BA%BB%E7%9C%81%E7%90%86%E5%B7%A5%E5%AD%A6%E9%99%A2

进去之后我们发现xpath helper和右击都是无法操作的
在这里插入图片描述
先找到我们要提取的内容
在这里插入图片描述
所以我们试试requests.get请求这个网址


import requests
from lxml import html

r = requests.get("http://www.qianmu.org/%E9%BA%BB%E7%9C%81%E7%90%86%E5%B7%A5%E5%AD%A6%E9%99%A2")
e = html.etree
r_text = e.HTML(r.text)

print(r.text)

先提取大学的名字

//div[@class="wikiContent"]/h1/text()

在这里插入图片描述
再分析代码,我们找到的内容都在 < div class=“infobox”> 里面
在这里插入图片描述

2.2、现在开始写代码

先取每个学校的校名运行试试

import requests
from lxml import html
r = requests.get("http://www.qianmu.org/ranking/1528.htm")
e = html.etree
r_text = e.HTML(r.text)

# 取每个学校详情的url
urls = r_text.xpath("//div[@class='rankItem']//a/@href")

for url in urls:
    r1 = requests.get(url)
    e1 = html.etree
    r1_text = e1.HTML(r1.text)
    
    # 取学校校名
    name = r1_text.xpath("//div[@class='wikiContent']/h1/text()")
    print(name)

在这里插入图片描述
把每个学校的详情介绍保存成一个字典data{ }

import requests
from lxml import html

r = requests.get("http://www.qianmu.org/ranking/1528.htm")
e = html.etree
r_text = e.HTML(r.text)

# 每个大学的详情页链接
urls = r_text.xpath("//div[@class='rankItem']//tr/td[2]//a/@href")

for url in urls:
    r1 = requests.get(url)
    e1 = html.etree
    r1_text = e1.HTML(r1.text)

    data = {}

    # 大学名字
    data['name'] = r1_text.xpath("//div[@class='wikiContent']/h1/text()")

    # 表格第一列
    k = r1_text.xpath("//div[@class='wikiContent']//table//td[1]/p/text()")

    # 表格第二列, 第二列有几排,所以先找到td[2]节点,再依次拼接
    cols = r1_text.xpath("//div[@class='wikiContent']//table//td[2]")
    v = [" ".join(col.xpath('.//text()')) for col in cols]
    if len(k) != len(v):
        continue

    # 把第一列、第二列放入字典
    data.update(zip(k, v))

    print(data)

把代码变得好看点

import requests
from lxml import html


def u(url):
    """请求并下载网页"""
    r = requests.get(url)
    if r.status_code != 200:
        r.raise_for_status()
    return r.text.replace('\t', '')


def parse_univerity(url):
    """处理大学详情页面"""
    u_text = html.etree.HTML(u(url))
    data = {}
    data['name'] = u_text.xpath("//div[@class='wikiContent']/h1/text()")
    table = u_text.xpath("//div[@class='wikiContent']//table")[0]
    k = table.xpath(".//td[1]/p/text()")
    cols = table.xpath(".//td[2]")
    v = [" ".join(col.xpath('.//text()')) for col in cols]
    # if len(k) != len(v):
    #     return None
    data.update(zip(k, v))
    return data


def process_data(data):
    """处理数据"""
    if data:
        print(data)


if __name__ == '__main__':
    # 1、请求入口页面
    r_text = html.etree.HTML(u("http://www.qianmu.org/ranking/1528.htm"))
    # 2、提取链接
    urls = r_text.xpath("//div[@class='rankItem']//tr/td[2]//a/@href")
    for url in urls:
        if not url.startswith("http://www.qianmu.org"):
            url = "http://www.qianmu.org/%s" % url
        # 3、提取详情页信息
        data = parse_univerity(url)
        process_data(data)

3、多线程

import time
import requests
import threading
from queue import Queue
from lxml import html

link_queue = Queue()
t_num = 10
# 线程池
t_s = []
p = 0


def u(url):
    """请求并下载网页"""
    r = requests.get(url)
    if r.status_code != 200:
        r.raise_for_status()
    global p
    p += 1
    return r.text.replace('\t', '')


def parse_univerity(url):
    """处理大学详情页面"""
    u_text = html.etree.HTML(u(url))
    data = {}
    data['name'] = u_text.xpath("//div[@class='wikiContent']/h1/text()")
    table = u_text.xpath("//div[@class='wikiContent']//table")[0]
    k = table.xpath(".//td[1]/p/text()")
    cols = table.xpath(".//td[2]")
    v = [" ".join(col.xpath('.//text()')) for col in cols]
    # if len(k) != len(v):
    #     return None
    data.update(zip(k, v))
    return data


def process_data(data):
    """处理数据"""
    if data:
        print(data)


def download():
    while True:
        # 阻塞直到从队列里获取一条消息
        link = link_queue.get()
        if link is None:
            break
        data = parse_univerity(link)
        process_data(data)
        link_queue.task_done()
        print("队列剩余:%s" % link_queue.qsize())


if __name__ == '__main__':
    start_time = time.time()
    # 1、请求入口页面
    r_text = html.etree.HTML(u("http://www.qianmu.org/ranking/1528.htm"))
    # 2、提取链接
    urls = r_text.xpath("//div[@class='rankItem']//tr/td[2]//a/@href")
    for url in urls:
        if not url.startswith("http://www.qianmu.org"):
            url = "http://www.qianmu.org/%s" % url
        # 3、提取详情页信息
        link_queue.put(url)
    # 启动线程,并将线程放入一个列表保存
    for i in range(t_num):
        t = threading.Thread(target=download())
        t.start()
        t_s.append(t)
    # 阻塞队列,直到队列被清空
    link_queue.join()
    # 向队列发送n个None,以通知线程退出
    for i in range(t_num):
        link_queue.put(None)
    # 退出线程
    for t in t_s:
        t.join()
    cost_seconds = time.time() - start_time
    print("线程执行完毕,共下载%s页,消耗%s秒" %
          (p, cost_seconds))

4、线程存入reids缓存

安装redis:pip install redis
启动redis:redis-server
在这里插入图片描述

Redis Sadd 命令将一个或多个成员元素加入到集合中,已经存在于集合的成员元素将被忽略。
Redis Rpush 命令用于将一个或多个值插入到列表的尾部(最右边)。
Redis Lpop 命令用于移除并返回列表的第一个元素。

第一次执行把所有的url都存入qianmu.seen和qianmu.queue
然后执行一个url在qianmu.queue中删除一个

import time
import requests
import threading
from queue import Queue
from lxml import html
import redis
import signal

link_queue = Queue()
t_num = 10
# 线程池
t_s = []
p = 0
rd = redis.Redis()
t_on = True

def u(url):
    """请求并下载网页"""
    r = requests.get(url)
    if r.status_code != 200:
        r.raise_for_status()
    global p
    p += 1
    return r.text.replace('\t', '')


def parse_univerity(url):
    """处理大学详情页面"""
    u_text = html.etree.HTML(u(url))
    data = {}
    data['name'] = u_text.xpath("//div[@class='wikiContent']/h1/text()")
    table = u_text.xpath("//div[@class='wikiContent']//table")[0]
    k = table.xpath(".//td[1]/p/text()")
    cols = table.xpath(".//td[2]")
    v = [" ".join(col.xpath('.//text()')) for col in cols]
    # if len(k) != len(v):
    #     return None
    data.update(zip(k, v))
    return data


def process_data(data):
    """处理数据"""
    if data:
        print(data)


def download(i):
    while t_on:
        # 阻塞直到从队列里获取一条消息

        # lpop从左边开始取
        link = rd.lpop("qianmu.queue")
        if link:
            data = parse_univerity(link)
            process_data(data)
            print("队列剩余:%s" % rd.llen("qianmu.queue"))
        time.sleep(0.2)
    print("%s号线程退出" % i)


def s_handler(signum, frame):
    print("按ctrl+c退出")
    global t_on
    t_on = False


if __name__ == '__main__':
    start_time = time.time()
    # 1、请求入口页面
    r_text = html.etree.HTML(u("http://www.qianmu.org/ranking/1528.htm"))
    # 2、提取链接
    urls = r_text.xpath("//div[@class='rankItem']//tr/td[2]//a/@href")
    for url in urls:
        if not url.startswith("http://www.qianmu.org"):
            url = "http://www.qianmu.org/%s" % url
        # 3、提取详情页信息

        # 如果往这里面添加成功,证明这个链接我们还没有爬过
        if rd.sadd("qianmu.seen", url):
            rd.rpush("qianmu.queue", url)

    # 启动线程,并将线程放入一个列表保存
    for i in range(t_num):
        t = threading.Thread(target=download(i), args=(i+1,))
        t.start()
        t_s.append(t)
    signal.signal(signal.SIGINT, s_handler)
    # 阻塞队列,直到队列被清空
    link_queue.join()
    # 向队列发送n个None,以通知线程退出
    for i in range(t_num):
        link_queue.put(None)
    # 退出线程
    for t in t_s:
        t.join()
    cost_seconds = time.time() - start_time
    print("线程执行完毕,共下载%s页,消耗%s秒" %
          (p, cost_seconds))

执行代码,然后中途点暂停
在这里插入图片描述
新打开一个cmd窗口,我们查看一下执行到哪个线程了
在这里插入图片描述
重新运行我们写的爬虫,让所有线程跑完

在这里插入图片描述
给qianmu.queue里面添加一个大学的详情页网址它会自动爬取
在这里插入图片描述

清空redis:flskdb

5、定义python文件启动参数

当没有第二个参数时,只启动进程不工作
有第二个参数时才开始取详情页

import sys
import time
import requests
import threading
from queue import Queue
from lxml import html
import redis
import signal

u_url = "http://www.qianmu.org/ranking/1528.htm"
link_queue = Queue()
t_num = 10
# 线程池
t_s = []
p = 0
rd = redis.Redis()
t_on = True

def u(url):
    """请求并下载网页"""
    try:
        r = requests.get(url,timeout=10)
        if r.status_code != 200:
            r.raise_for_status()
        global p
        p += 1
        return r.text.replace('\t', '')
    except Exception:
        print("下载网页出错%s" % url)


def parse_univerity(url):
    """处理大学详情页面"""
    u_text = html.etree.HTML(u(url))
    data = {}
    data['name'] = u_text.xpath("//div[@class='wikiContent']/h1/text()")
    table = u_text.xpath("//div[@class='wikiContent']//table")[0]
    k = table.xpath(".//td[1]/p/text()")
    cols = table.xpath(".//td[2]")
    v = [" ".join(col.xpath('.//text()')) for col in cols]
    # if len(k) != len(v):
    #     return None
    data.update(zip(k, v))
    return data


def process_data(data):
    """处理数据"""
    if data:
        print(data)


def download(i):
    while t_on:
        # 阻塞直到从队列里获取一条消息

        # lpop从左边开始取
        link = rd.lpop("qianmu.queue")
        if link:
            data = parse_univerity(link)
            process_data(data)
            print("队列剩余:%s" % rd.llen("qianmu.queue"))
        time.sleep(0.2)
    print("%s号线程退出" % i)


def s_handler(signum, frame):
    print("按ctrl+c退出")
    global t_on
    t_on = False


if __name__ == '__main__':
    start_time = time.time()
    # 如果参数大于1个
    if len(sys.argv) > 1:
        # 设置u_url为第二个参数
        u_url = sys.argv[1]
        # 1、请求入口页面
        r_text = html.etree.HTML(u(u_url))
        # 2、提取链接
        urls = r_text.xpath("//div[@class='rankItem']//tr/td[2]//a/@href")
        for url in urls:
            if not url.startswith("http://www.qianmu.org"):
                url = "http://www.qianmu.org/%s" % url
            # 3、提取详情页信息

            # 如果往这里面添加成功,证明这个链接我们还没有爬过
            if rd.sadd("qianmu.seen", url):
                rd.rpush("qianmu.queue", url)
    else:
        # 启动线程,并将线程放入一个列表保存
        for i in range(t_num):
            t = threading.Thread(target=download(i), args=(i+1,))
            t.start()
            t_s.append(t)
        signal.signal(signal.SIGINT, s_handler)
        # 阻塞队列,直到队列被清空
        link_queue.join()
        # 向队列发送n个None,以通知线程退出
        for i in range(t_num):
            link_queue.put(None)
        # 退出线程
        for t in t_s:
            t.join()
        cost_seconds = time.time() - start_time
        print("线程执行完毕,共下载%s页,消耗%s秒" %
              (p, cost_seconds))

在这里插入图片描述
在这里插入图片描述

发布了136 篇原创文章 · 获赞 30 · 访问量 7074

猜你喜欢

转载自blog.csdn.net/a__int__/article/details/104663988
今日推荐