python爬虫中遇到列表加载中 python爬虫列表搜索_xml


原创 麦自香 Python爬虫案例

当我们初学爬虫的时候,我们都会选择一些最基本的网站,往往不带任何反爬措施。比如某个博客站点,我们要爬全站的话,就顺着列表页爬到文章页,再把文章的时间、作者、正文等信息爬下来就可以了。那代码怎么写呢?

一般的情况我们都是使用 Python 的 requests 等库就够了,写一个基本的逻辑,顺着把一篇篇文章的源码获取下来,解析的话会用到 XPath、BeautifulSoup、或者正则表达式,再把文本写入存下来就完事了。代码很简单,就几个方法调用。逻辑很简单,几个循环加存储。

之所以这么说,是因为接下来我们想要对古诗文网站来进行爬取,分别用正则表达式、BeautifulSoup、Xpath、通过这些代码来给大家介绍下Python常用的选择器,以及他们之间的区别,让我们对Python的选择器有一个更深的认识。

一、正则表达式

正则表达式为我们提供了抓取数据的快捷方式。虽然该正则表达式更容易适应未来变化,但又存在难以构造、可读性差的问题。接下来我们使用正则表达式爬取古诗文网站,代码如下:

# 使用正则表达式解析网页元素
# 关键点:直接找每个个体里面相同位置的元素,用findall一次提取出来到列表中
import requests
import re
DATA = []
def getHTMLtext(url, headers, timeout=10):
 try:
 resp = requests.get(url, headers=headers, timeout=timeout)
 resp.raise_for_status
 resp.encoding = 'utf-8'
 return resp.text
 except:
 return ''
def reParser(text):
 name_list = re.findall(r'<div class="yizhu".*?<b>(.*?)</b>', text, re.S) # re.DOTALL
 dynasty_list = re.findall(r'<p class="source">.*?target="_blank">(.*?)</a>', text, re.S)
 author_list = re.findall(r'<p class="source">.*?target="_blank">.*?</a>.*?target="_blank">(.*?)</a>', text, re.S)
 row_content_list = re.findall(r'<div class="contson".*?>(.*?)</div>', text, re.S)
 content_list = []
 for content in row_content_list:
 temp = re.sub(r'<.*?>', '', content) # 这里一定要记得不要写成了贪婪匹配哦
 content_list.append(temp.strip()) # 去除空格
 for value in zip(name_list, dynasty_list, author_list, content_list):
 name, dynasty, author, content = value
 poetry_dict = {
 '诗词名': name,
 '朝代': dynasty,
 '作者': author,
 '内容': content,
 }
 DATA.append(poetry_dict)
def print_poetry(data):
 for every_poetry in data:
 print(every_poetry['诗词名'])
 print(every_poetry['朝代'] + ':' + every_poetry['作者'])
 print(every_poetry['内容'])
 print("n" + '*' * 50 + "n")
if __name__ == '__main__':
 row_url = '网页发生错误'
 headers = {
 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36'}
 num = input('请输入要爬取的页数(1-100):')
 for i in range(eval(num)):
 url = row_url.format(i + 1)
 # print(url)
 text = getHTMLtext(url, headers)
 if text == '':
 print('url: {} 访问失败'.format(url))
 else:
 reParser(text)
 TOP10 = DATA[:10]
 # print(TOP10)
 print_poetry(TOP10)

利用正则表达式实现对目标信息的精准采集,此外 ,我们都知道,网页时常会产生变更,导致网页中会发生一些微小的布局变化时,此时也会使得之前写好的正则表达式无法满足需求,而且还不太好调试。当需要匹配的内容有很多的时候,使用正则表达式提取目标信息会导致程序运行的速度减慢,需要消耗更多内存。

二、BeautifulSoup

BeautifulSoup是一个非常流行的 Pyhon 模块,简称Bs4。该模块可以解析网页,并提供定位内容的便捷接口。通过

pip install beautifulsoup4

就可以实现该模块的安装了。演示代码如下:

# 使用bs4提取网页,先利用find_all解析
import requests
from bs4 import BeautifulSoup
DATA = []
def getHTMLtext(url, headers, timeout=10):
 try:
 resp = requests.get(url, headers=headers, timeout=timeout)
 resp.raise_for_status
 resp.encoding = 'utf-8'
 return resp.text
 except:
 return ''
def bs4_find_all_Parser(text):
 soup = BeautifulSoup(text, 'lxml')
 sons = soup.find_all('div', class_="sons")[:10] # 返回一个<class 'bs4.element.ResultSet'>,每一个元素都是Tag类型
 # 注意:上一步里面返回了一些其他的元素,我们可以提取出前面的10项,那是我们需要用到的
 # print(sons)
 for son in sons:
 name = son.find('b').get_text()
 print(name)
 dynasty_author = son.find('p', class_="source").get_text()
 print(dynasty_author)
 content = son.find('div', class_="contson").get_text()
 print(content)
if __name__ == '__main__':
 url = '古诗文网-古诗文经典传承'
 headers = {
 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36'}
 text = getHTMLtext(url, headers)
 if text == '':
 print('url: {} 访问失败'.format(url))
 else:
 bs4_find_all_Parser(text)

使用 Bs4的第一步是将己下载的 HTML 内容解析为 soup文档。由于大多数网 页都不具备良好的HTML 格式,此Bs4需要对实际格式进行确定。Bs4能够正确解析缺失的引号并闭合标签,此外还会添加<html >和<body>标签使其成为完整的HTML文档。通常使用find() 和find_all()方法来定位我们需要的元素。

如果你想了解BeautifulSoup全部方法和参数,可以查阅BeautifulSoup的官方文档。虽然BeautifulSoup在代码的理解上比正则表达式要复杂一些,但是其更加容易构造和理解。

三、lxml

Xpath,使lxml 模块的第一步和BeautifulSoup一样,也是将有可能不合法的HTML 解析为统一格式。虽然lxml可以正确解析属性两侧缺失的引号,并闭合标签,不过该模块没有额外添加<html >和<body>标签 。

lxml也是需要提前安装的,通过:

pip install lxml
演示代码如下:
# xpath代码展示
import requests
from lxml import etree
DATA = []
def getHTMLtext(url, headers, timeout=10):
 try:
 resp = requests.get(url, headers=headers, timeout=timeout)
 resp.raise_for_status
 resp.encoding = 'utf-8'
 return resp.text
 except:
 return ''
def xpathParser(text):
 htmlElement = etree.HTML(text) # <class 'lxml.etree._Element'>
 name_list = htmlElement.xpath('/html/body/div[2]/div[1]/div/div[1]/p[1]/a/b/text()')
 dynasty_list = htmlElement.xpath('/html/body/div[2]/div[1]/div/div[1]/p[2]/a[1]/text()')
 author_list = htmlElement.xpath('/html/body/div[2]/div[1]/div/div[1]/p[2]/a[2]/text()')
 content_list = []
 poetries = htmlElement.xpath(
 '//div[@class="contson" and contains(@id,"contson")]') # 返回一个列表,里面每一个都是'lxml.etree._Element'
 # print(etree.tostring(poetries[0],encoding = 'utf-8').decode('utf-8'))
 for poetry in poetries:
 row_content = ''.join(poetry.xpath('.//text()')) # 这里的.可千万不能掉,否则会忽略掉poetry哦
 content_list.append(row_content.replace('n', ''))
 for value in zip(name_list, dynasty_list, author_list, content_list):
 name, dynasty, author, content = value
 poetry_dict = {
 '诗词名': name,
 '朝代': dynasty,
 '作者': author,
 '内容': content,
 }
 DATA.append(poetry_dict)
def print_poetry(data):
 for every_poetry in data:
 print(every_poetry['诗词名'])
 print(every_poetry['朝代'] + ':' + every_poetry['作者'])
 print(every_poetry['内容'])
 print("n" + '*' * 50 + "n")
if __name__ == '__main__':
 row_url = '网页发生错误'
 headers = {
 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36'}
 num = input('请输入要爬取的页数(1-100):')
 for i in range(eval(num)):
 url = row_url.format(i + 1)
 text = getHTMLtext(url, headers)
 if text == '':
 print('url: {} 访问失败'.format(url))
 else:
 xpathParser(text)
 TOP10 = DATA[:10]
 print_poetry(TOP10)

在线复制Xpath表达式可以很方便的复制Xpath表达式。但是通过该方法得到的Xpath表达式放在程序中一般不能用,而且长的没法看。所以Xpath表达式一般还是要自己亲自上手。

如果你的爬虫瓶颈是下载网页,也不是抽取数据的话,那么使用较慢的方法(如BeautifulSoup) 也不成问题。如果只需抓取少量数据,并且想要避免额外依赖的话,那么正则表达式可能更加适合。不过,通常情况下,lxml是抓取数据的最好选择,这是因为该方法既快速又健壮,而正则表达式和BeautifulSoup只在某些特定场景下有用。

Lxml模块使用 C语言编写,其解析速度比 BeautiflSoup更快,而且其安装过程也更为复杂,在此小编就不赘述啦。XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的。