文章目录
- 认识网络爬虫
- HTML页面组成
- Requests模块get请求与实战
- 效果图
- 代码解析
- Post请求与实战
- 代码解析
- 发送JSON格式的POST请求
- 使用代理服务器发送POST请求
- 发送带文件的POST请求
- Xpath解析
- XPath语法的规则集:
- XPath解析的代码案例及其详细讲解:
- 使用XPath解析HTML文档
- 使用XPath解析XML文档
- 处理命名空间的XPath解析
- BeautifulSoup详讲与实战
- 创建BeautifulSoup对象
- 遍历文档树
- 搜索文档树
- 获取节点属性和文本内容
- BeautifulSoup 代码案例讲解。
- 解析HTML文档并获取标题
- 遍历文档树并获取所有段落内容
- 使用CSS选择器搜索文档树
- 使用正则表达式搜索文档树
- 解析XML文档并获取节点信息
- 修改节点属性
- 正则表达式
- 正则表达式知识点
- 正则表达式案例
- 正则表达式实战
- 字体反爬
- Scrapy入门
- Scrapy实例
- 完结
认识网络爬虫
网络爬虫是指一种程序自动获取网页信息的方式,它能够自动化地获取互联网上的数据。通过使用网络爬虫,我们可以方便地获取到网络上的各种数据,例如网页链接、文本、图片、音频、视频等等。
HTML页面组成
网页是由HTML标签和内容组成,HTML标签通过标签属性可以定位到需要的内容。网页中的样式由CSS控制,JavaScript可以实现网页动态效果。
HTML标签是一种用于构建Web页面的标记语言,它描述了页面的结构和元素。HTML标签通常包含一个起始标签和一个结束标签,例如<div>
和</div>
。HTML标签也可以包含属性,属性用于提供有关元素的额外信息。例如,<a>
元素的href
属性指定了链接目标的URL地址,而<img>
元素的src
属性指定了要显示的图像文件的URL地址。
CSS是一种用于控制Web页面样式的样式表语言,它可以为HTML元素提供样式和布局。通过CSS,我们可以控制文本的字体、颜色、大小和样式,以及元素的大小、位置、边框和背景等。
JavaScript是用于实现Web页面动态效果的一种编程语言,它可以实现网页上的各种交互效果,例如弹出窗口、表单验证、动画效果等。
Requests模块get请求与实战
Requests是Python中的HTTP库,提供了简洁易用的接口进行HTTP请求。其中,GET请求常用于获取静态网页信息。
我们通过requests.get()方法来发送一个GET请求.
import requestsurl = 'https://www.baidu.com'
response = requests.get(url)
print(response.text)
效果图
代码解析
第一行导入了requests模块,第二行指定了要请求的URL地址,在本例中我们使用百度首页作为示例。第三行使用requests库的get()方法来获取该URL的响应对象。响应对象包含了服务器返回的所有信息,包括Header(头部)和Body(主体)两部分。其中Header包含了很多信息,如日期、内容类型、服务器版本等,而Body包含了页面HTML源代码等具体信息。
第四行使用print()函数打印出响应内容的文本形式。运行这段代码,我们就可以在终端中看到百度首页的HTML源代码。
在实际爬虫中,我们可以利用requests模块的一些属性或者方法来解析响应内容,提取需要的数据。例如,可以使用response.status_code属性来获取HTTP状态码,使用response.headers属性来获取HTTP头部信息等。此外,我们还可以使用response.json()方法来解析JSON格式的响应内容,使用response.content方法来获取字节形式的响应内容等。
Post请求与实战
POST请求与GET请求的区别在于,POST请求会将请求参数放在请求体中,而GET请求则将请求参数放在URL中。通常情况下,POST请求比GET请求更安全,因为它可以隐藏请求参数。
我们通过requests.post()方法来发送一个POST请求,下面是详细的代码分析:
import requestsurl = 'http://xxxx.org/post' # 这里使用xxxx.org来演示POST请求
data = {'key1': 'value1', 'key2': 'value2'}
response = requests.post(url, data=data)
print(response.text)
代码解析
第一行导入了requests模块,
第二行指定了要请求的URL地址 。
第三行定义了请求参数data,这个字典中包含了两个键值对,分别表示key1和key2这两个参数的值。第四行使用requests库的post()方法来发送POST请求并获取响应对象。
在实际爬虫中,我们可以利用requests模块的一些属性或者方法来解析响应内容,提取需要的数据。
发送JSON格式的POST请求
import requests
import jsonurl = 'http://xxxx.org/post' # 这里使用xxxx.org来演示POST请求
data = {'key1': 'value1', 'key2': 'value2'}
headers = {'Content-Type': 'application/json'}
response = requests.post(url, data=json.dumps(data), headers=headers)
print(response.text)
在这个案例中,我们将请求参数data转换成JSON格式,并使用headers来指定Content-Type为application/json。然后,我们通过requests库的post()方法来发送POST请求。
使用代理服务器发送POST请求
import requestsproxy = {'http': 'http://10.10.1.10:3128','https': 'https://10.10.1.10:1080'
}
url = 'http://xxxx.org/post' # 这里使用xxxx.org来演示POST请求
data = {'key1': 'value1', 'key2': 'value2'}
response = requests.post(url, data=data, proxies=proxy)
print(response.text)
发送带文件的POST请求
import requestsurl = 'http://xxxx.org/post' # 这里使用xxxx.org来演示POST请求
files = {'file': open('myfile.txt', 'rb')}
response = requests.post(url, files=files)
print(response.text)
Xpath解析
XPath语法的规则集:
表达式 | 描述 |
---|---|
nodename | 选择所有名为nodename的元素 |
/ | 从当前节点选取根节点 |
// | 从当前节点选取任意节点 |
. | 选择当前节点 |
… | 选择当前节点的父节点 |
@ | 选择属性 |
* | 匹配任何元素节点 |
[@attrib] | 选择具有给定属性的所有元素 |
[@attrib=‘value’] | 选择具有给定属性值的所有元素 |
tagname[text() = ‘text’] | 选择具有给定文本的所有tagname元素 |
XPath解析的代码案例及其详细讲解:
使用XPath解析HTML文档
from lxml import etree
import requestsurl = 'https://www.baidu.com'
html = requests.get(url).text
selector = etree.HTML(html)
result = selector.xpath('//title/text()')
print(result[0])
使用XPath解析XML文档
from lxml import etreexml = '''
<bookstore><book category="cooking"><title lang="en">Everyday Italian</title><author>Giammarco Tomaselli</author><year>2010</year><price>30.00</price></book><book category="children"><title lang="en">Harry Potter</title><author>J.K. Rowling</author><year>2005</year><price>29.99</price></book>
</bookstore>
'''selector = etree.XML(xml)
result = selector.xpath('//book[1]/title/text()')
print(result[0])
处理命名空间的XPath解析
from lxml import etreexml = '''
<ns:bookstore xmlns:ns="http://www.example.com"><ns:book category="cooking"><ns:title lang="en">Everyday Italian</ns:title><ns:author>Giammarco Tomaselli</ns:author><ns:year>2010</ns:year><ns:price>30.00</ns:price></ns:book><ns:book category="children"><ns:title lang="en">Harry Potter</ns:title><ns:author>J.K. Rowling</ns:author><ns:year>2005</ns:year><ns:price>29.99</ns:price></ns:book>
</ns:bookstore>
'''selector = etree.XML(xml)
ns = {'ns': 'http://www.example.com'}
result = selector.xpath('//ns:book[1]/ns:title/text()', namespaces=ns)
print(result[0])
BeautifulSoup详讲与实战
创建BeautifulSoup对象
首先我们需要导入BeautifulSoup模块:
from bs4 import BeautifulSoup
使用BeautifulSoup对HTML文档进行解析,可以通过以下两种方式:
(1) 传递一个HTML字符串作为参数:
html_doc = """
<html>
<head><title>这是标题</title></head>
<body>
<p class="para1">第一段落</p>
<p class="para2">第二段落</p>
</body>
</html>
"""soup = BeautifulSoup(html_doc, 'html.parser')
(2) 传递一个文件路径或文件对象作为参数:
with open('example.html', 'r') as f:soup = BeautifulSoup(f, 'html.parser')
遍历文档树
(1) .contents:返回一个包含所有子节点的列表。
for child in soup.body.contents:print(child)
(2) .children:返回一个包含所有子节点的迭代器。
for child in soup.body.children:print(child)
(3) .descendants:返回一个包含文档树中所有子孙节点的迭代器。
for element in soup.descendants:print(element)
(4) .parent:返回一个节点的父节点。
p = soup.body.p
print(p.parent)
(5) .parents:返回一个包含节点所有祖先节点的迭代器。
p = soup.body.p
for parent in p.parents:print(parent.name)
搜索文档树
(1) .find_all():返回一个满足条件的节点列表。
soup.find_all('p', class_='para1')
soup.find_all('p', {'class': 'para1'}, string='第一段落')
(2) .find():返回第一个满足条件的节点。
soup.find('p', class_='para1')
soup.find('p', {'class': 'para1'}, string='第一段落')
(3) .select():使用CSS选择器语法返回满足条件的节点列表。
soup.select('p.para1')
soup.select('p[class="para1"]')
获取节点属性和文本内容
(1) .get():获取节点的指定属性。
p = soup.find('p', class_='para1')
print(p.get('class'))
(2) .text:获取节点的文本内容。
p = soup.find('p', class_='para1')
print(p.text)
(3) .string:获取节点的文本内容(如果节点只有一个子节点且该子节点是字符串类型)。
p = soup.find('p', class_='para1')
print(p.string)
BeautifulSoup 代码案例讲解。
解析HTML文档并获取标题
from bs4 import BeautifulSoup
import requestsurl = 'https://www.baidu.com'
html = requests.get(url).text
soup = BeautifulSoup(html, 'html.parser')
title = soup.title.string
print(title)
遍历文档树并获取所有段落内容
from bs4 import BeautifulSouphtml_doc = """
<html>
<head><title>这是标题</title></head>
<body>
<p class="para1">第一段落</p>
<p class="para2">第二段落</p>
</body>
</html>
"""soup = BeautifulSoup(html_doc, 'html.parser')
for p in soup.body.children:if p.name == 'p':print(p.string)
使用CSS选择器搜索文档树
from bs4 import BeautifulSouphtml_doc = """
<html>
<head><title>这是标题</title></head>
<body>
<p class="para1">第一段落</p>
<p class="para2">第二段落</p>
</body>
</html>
"""soup = BeautifulSoup(html_doc, 'html.parser')
p_list = soup.select('p.para1')
for p in p_list:print(p.text)
使用正则表达式搜索文档树
import re
from bs4 import BeautifulSouphtml_doc = """
<html>
<head><title>这是标题</title></head>
<body>
<p class="para1">第一段落</p>
<p class="para2">第二段落</p>
</body>
</html>
"""soup = BeautifulSoup(html_doc, 'html.parser')
pattern = re.compile('^p.*?1$') # 匹配所有以p开头并且以1结尾的类名
p_list = soup.find_all(class_=pattern)
for p in p_list:print(p.text)
解析XML文档并获取节点信息
from bs4 import BeautifulSoupxml_doc = """
<?xml version="1.0" encoding="UTF-8"?>
<data><country name="Liechtenstein"><rank>1</rank><year>2008</year><gdppc>141100</gdppc><neighbor name="Austria" direction="E"/><neighbor name="Switzerland" direction="W"/></country><country name="Singapore"><rank>4</rank><year>2011</year><gdppc>59900</gdppc><neighbor name="Malaysia" direction="N"/></country>
</data>
"""soup = BeautifulSoup(xml_doc, 'xml')
for country in soup.find_all('country'):print(country['name'])print(country.rank.text)print(country.year.text)print(country.gdppc.text)for neighbor in country.find_all('neighbor'):print(neighbor['name'], neighbor['direction'])
修改节点属性
from bs4 import BeautifulSouphtml_doc = """
<html>
<head><title>这是标题</title></head>
<body>
<p class="para1">第一段落</p>
<p class="para2">第二段落</p>
</body>
</html>
"""soup = BeautifulSoup(html_doc, 'html.parser')
p = soup.find('p', class_='para1')
p['class'] = 'new_class'
print(p)
正则表达式
正则表达式知识点
正则表达式是一种用于匹配字符串的模式。它通过字符组成规则定义了搜索文本中特定模式的方法。Python中的re模块提供了使用正则表达式的功能。
- . 表示任意字符。
- \d表示数字,\D表示非数字。
- \w表示单词字符,即az、AZ、0~9和下划线。
- \W表示非单词字符。
- \s表示空白符,包括空格、制表符、换行符等。
- \S表示非空白符。
- ^表示匹配行首。
- $表示匹配行尾。
- *表示匹配前面的字符零次或多次。
- +表示匹配前面的字符一次或多次。
- ?表示匹配前面的字符零次或一次。
- {m}表示匹配前面的字符m次。
- {m,n}表示匹配前面的字符m到n次。
- […]表示匹配方括号中任意一个字符。
- [^…]表示匹配除了方括号中给出的字符以外的任意一个字符。
- (…)表示匹配括号中的表达式。
- re.match():从字符串的开头开始匹配,只匹配一次。
- re.search():在字符串中匹配第一个符合条件的内容。
- re.findall():在字符串中匹配所有符合条件的内容并以列表的形式返回。
- re.sub():用一个新的字符串替换掉匹配到的所有内容。
- re.compile():将正则表达式转化为一个正则表达式对象,以便于复用。
正则表达式案例
(1) 匹配手机号码
import rephone_nums = ['13912345678', '13812345678', '13512345678', '13612345678', '13712345678']pattern = r'^1[3-9]\d{9}$'
for phone_num in phone_nums:if re.match(pattern, phone_num):print(f'{phone_num}是一个合法的手机号码')else:print(f'{phone_num}不是一个合法的手机号码')
(2) 替换HTML文档中的标签
import rehtml_doc = """
<html>
<head><title>这是标题</title></head>
<body>
<p class="para1">第一段落</p>
<p class="para2">第二段落</p>
</body>
</html>
"""pattern = r'<.*?>'
new_doc = re.sub(pattern, '', html_doc)
print(new_doc)
(3) 提取金融数据
import retext = '2019年GDP增速为7.5%,同比增长0.3个百分点;CPI同比上涨2.5%,环比上涨0.3%。'pattern1 = r'\d+.\d+%'
pattern2 = r'[A-Z]+'
num_list = re.findall(pattern1, text)
unit_list = re.findall(pattern2, text)
for i in range(len(num_list)):print(f'{num_list[i]} {unit_list[i]}')
正则表达式实战
import os
import re
from collections import Counterdef get_word_counts(folder_path):"""统计指定文件夹中所有文本文件中各个单词的出现频率,并返回一个Counter对象。"""word_counter = Counter()for root, dirs, files in os.walk(folder_path):for file in files:if file.endswith('.txt'):file_path = os.path.join(root, file)# 读取文本文件内容with open(file_path, 'r', encoding='utf-8') as f:text = f.read()# 使用正则表达式去除标点符号、换行符等非单词字符pattern = r'\b\w+\b'words = re.findall(pattern, text)# 对单词列表进行计数,并将结果更新到Counter对象中word_counter.update(words)return word_counterif __name__ == '__main__':folder_path = 'test'word_counter = get_word_counts(folder_path)# 输出前十个出现频率最高的单词及其出现次数top_n = 10print(f'Top {top_n} words:')for word, count in word_counter.most_common(top_n):print(f'{word:<10} {count}')
字体反爬
- 解析woff文件
很多网站会使用woff格式的字体文件来渲染文本内容,爬虫需要先下载这些字体文件,并解析出字符与字形之间的对应关系,然后才能正常解密文本内容。
- 使用fontTools库
Python中有一个非常优秀的字体解析库叫做fontTools,可以帮助我们轻松地解析字体文件,并生成字形对应表。使用该库可以避免自行解析字体文件所遇到的各种问题。
- 使用在线字体解密工具
有些网站提供了在线字体解密工具,如FontSpider、字体反爬插件等,可以帮助我们快速地解密字体。不过,使用这种方法需要注意隐私安全问题。
(1) 解析woff文件
import base64
from fontTools.ttLib import TTFont# 下载字体文件并保存为base64编码字符串
font_url = 'http://example.com/font.woff'
font_base64 = '...'# 将base64编码字符串解码并保存到本地
with open('font.woff', 'wb') as f:font_data = base64.b64decode(font_base64)f.write(font_data)# 解析字体文件并获取字形对应表
font = TTFont('font.woff')
cmap = font.getBestCmap()# 定义替换规则
replace_dict = {'连': '0','': '1','﫵': '2','': '3','': '4','': '5','': '6','': '7','倫': '8','': '9',
}# 替换文本内容
text = ''
for key, value in replace_dict.items():text = text.replace(key, value)# 输出结果
print(text)
(2) 使用fontTools库
import requests
from io import BytesIO
from fontTools.ttLib import TTFont# 下载字体文件并用fontTools库读取
font_url = 'http://example.com/font.woff'
font_data = requests.get(font_url).content
font = TTFont(BytesIO(font_data))# 获取字形对应表
cmap = font.getBestCmap()# 定义替换规则
replace_dict = {'连': '0','': '1','﫵': '2','': '3','': '4','': '5','': '6','': '7','倫': '8','': '9',
}# 解密文本内容
text = ''
for key, value in replace_dict.items():glyph_id = cmap[int(key[3:-1], 16)]text = text.replace(key, value)# 输出结果
print(text)
(3) 使用在线字体解密工具
import requests
from fontSpider import FontSpider# 下载字体文件并保存为base64编码字符串
font_url = 'http://example.com/font.woff'
r = requests.get(font_url)
font_base64 = FontSpider(r.content).to_base64()# 使用在线字体解密工具解密文本内容
text = ''
response = requests.post('http://font-spider.com/api/v1/decrypt', data={'font': font_base64, 'text': text})
result = response.json()# 输出结果
print(result['data'])
需要注意的是,使用在线字体解密工具可能存在隐私安全问题,因此尽量避免在生产环境中使用。
Scrapy入门
- 工程结构
Scrapy的工程具有标准的项目结构,通常包含以下几个文件:
- scrapy.cfg:Scrapy项目配置文件。
- items.py:定义爬取的数据结构。
- middlewares.py:管理请求和响应,例如User-Agent、代理等。
- pipelines.py:配置到底怎么样后续处理item。
- settings.py:保存爬虫的参数设置。
- spiders/:保存爬虫代码的目录。
- 爬虫流程
Scrapy的爬虫流程如下:
- 发起请求:通过定义好的URL地址来发送HTTP请求。
- 下载页面:Scrapy会自动下载对应的页面,或使用第三方库,如requests、Selenium等。
- 解析页面:使用XPath或CSS选择器解析网页内容。
- 保存数据:将解析得到的数据保存到本地或数据库中。
- Scrapy组件
Scrapy具有以下几个重要组件:
- Spider:定义如何抓取某个站点,包括如何跟进链接、如何分析页面内容等。
- Item:定义爬取的数据结构。
- Pipeline:负责处理Item,如清理、过滤、存储到数据库等。
- Downloader:负责下载网页,并将结果传递给Spider。
- Scheduler:负责调度Spider发起请求,并将结果传递给Downloader。
Scrapy实例
- 爬取豆瓣电影TOP250的数据
import scrapyclass DoubanMovieSpider(scrapy.Spider):name = 'douban_movie'allowed_domains = ['movie.douban.com']start_urls = ['https://movie.douban.com/top250']def parse(self, response):for info in response.xpath('//div[@class="info"]'):yield {'title': info.xpath('div[@class="hd"]/a/span/text()').extract_first(),'score': info.xpath('div[@class="bd"]/div[@class="star"]/span[@class="rating_num"]/text()').extract_first(),'director': info.xpath('div[@class="bd"]/p/text()')[0].strip() if len(info.xpath('div[@class="bd"]/p')) == 2 else '','year': info.xpath('div[@class="bd"]/p/text()')[-1].strip().replace('(', '').replace(')', '') if len(info.xpath('div[@class="bd"]/p')) == 2 else info.xpath('div[@class="bd"]/p/text()')[-1].strip()}next_page = response.xpath('//span[@class="next"]/a/@href')if next_page:yield scrapy.Request(url=response.urljoin(next_page.extract_first()), callback=self.parse)
- 使用middlewares设置User-Agent
import random
from scrapy.downloadermiddlewares.useragent import UserAgentMiddlewareclass RandomUserAgentMiddleware(UserAgentMiddleware):user_agents = ['Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299','Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36','Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36','Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36','Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36','Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; AS; rv:11.0) like Gecko','Mozilla/5.0 (Windows NT 6.3; WOW64; rv:57.0) Gecko/20100101 Firefox/57.0','Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299']def process_request(self, request, spider):user_agent = random.choice(self.user_agents)request.headers.setdefault('User-Agent', user_agent)
- 将数据写入MySQL数据库
import pymysql
from scrapy.exceptions import DropItemclass MysqlPipeline(object):def __init__(self, mysql_host, mysql_db, mysql_user, mysql_pwd, mysql_table):self.mysql_host = mysql_hostself.mysql_db = mysql_dbself.mysql_user = mysql_userself.mysql_pwd = mysql_pwdself.mysql_table = mysql_tabledef process_item(self, item, spider):if item['title'] and item['score']:db = pymysql.connect(host=self.mysql_host, user=self.mysql_user, password=self.mysql_pwd, db=self.mysql_db)cursor = db.cursor()sql = "INSERT INTO %s (title, score, director, year) VALUES ('%s', '%s', '%s', '%s')" % (self.mysql_table, item['title'], item['score'], item['director'], item['year'])try:cursor.execute(sql)db.commit()except Exception as e:db.rollback()raise DropItem("Failed to insert item: {}".format(e))finally:db.close()return item
完结
本文链接:https://my.lmcjl.com/post/11612.html
4 评论