写在前面
一个简单的实例爬取世界大学排名列表: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))