数据采集和解析
通过上一个章节的讲解,我们已经了解到了开发一个爬虫需要做的工作以及一些常见的问题,下面我们给出一个爬虫开发相关技术的清单以及这些技术涉及到的标准库和第三方库,稍后我们会一一介绍这些内容。
- 下载数据 – urllib / requests / aiohttp / httpx
- 解析数据 – re / lxml / beautifulsoup4 / pyquery
- 缓存和持久化 – mysqlclient / sqlalchemy / peewee / redis / pymongo
- 生成数字签名 – hashlib。
- 序列化和压缩 – pickle / json / zlib
- 调度器 – multiprocessing / threading / concurrent.futures
HTML页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
<!DOCTYPE html> <html> <head> <title>Home</title> <style type="text/css"> /* 此处省略层叠样式表代码 */ </style> </head> <body> <div class="wrapper"> <header> <h1>Yoko's Kitchen</h1> <nav> <ul> <li><a href="" class="current">Home</a></li> <li><a href="">Classes</a></li> <li><a href="">Catering</a></li> <li><a href="">About</a></li> <li><a href="">Contact</a></li> </ul> </nav> </header> <section class="courses"> <article> <figure> <img src="images/bok-choi.jpg" alt="Bok Choi" /> <figcaption>Bok Choi</figcaption> </figure> <hgroup> <h2>Japanese Vegetarian</h2> <h3>Five week course in London</h3> </hgroup> <p>A five week introduction to traditional Japanese vegetarian meals, teaching you a selection of rice and noodle dishes.</p> </article> <article> <figure> <img src="images/teriyaki.jpg" alt="Teriyaki sauce" /> <figcaption>Teriyaki Sauce</figcaption> </figure> <hgroup> <h2>Sauces Masterclass</h2> <h3>One day workshop</h3> </hgroup> <p>An intensive one-day course looking at how to create the most delicious sauces for use in a range of Japanese cookery.</p> </article> </section> <aside> <section class="popular-recipes"> <h2>Popular Recipes</h2> <a href="">Yakitori (grilled chicken)</a> <a href="">Tsukune (minced chicken patties)</a> <a href="">Okonomiyaki (savory pancakes)</a> <a href="">Mizutaki (chicken stew)</a> </section> <section class="contact-details"> <h2>Contact</h2> <p>Yoko's Kitchen<br> 27 Redchurch Street<br> Shoreditch<br> London E2 7DP</p> </section> </aside> <footer> © 2011 Yoko's Kitchen </footer> </div> <script> /* 此处省略JavaScript代码 */ </script> </body> </html> |
如上所示的HTML页面通常由三部分构成,分别是用来承载内容的Tag(标签)、负责渲染页面的CSS(层叠样式表)以及控制交互式行为的JavaScript。通常,我们可以在浏览器的右键菜单中通过“查看网页源代码”的方式获取网页的代码并了解页面的结构;当然,我们也可以通过浏览器提供的开发人员工具来了解更多的信息。
使用requests获取页面
在上一节课的代码中我们使用了三方库requests来获取页面,下面我们对requests库的用法做进一步说明。
- GET请求和POST请求。
1 2 3 4 5 6 7 8 9 10 11 12 |
import requests resp = requests.get('http://www.baidu.com/index.html') print(resp.status_code) print(resp.headers) print(resp.cookies) print(resp.content.decode('utf-8')) resp = requests.post('http://httpbin.org/post', data={'name': 'Hao', 'age': 40}) print(resp.text) data = resp.json() print(type(data)) |
- URL参数和请求头。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
resp = requests.get( url='https://movie.douban.com/top250', headers={ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) ' 'AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/83.0.4103.97 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;' 'q=0.9,image/webp,image/apng,*/*;' 'q=0.8,application/signed-exchange;v=b3;q=0.9', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', } ) print(resp.status_code) |
- 复杂的POST请求(文件上传)。
1 2 3 4 5 |
resp = requests.post( url='http://httpbin.org/post', files={'file': open('data.xlsx', 'rb')} ) print(resp.text) |
- 操作Cookie。
1 2 3 4 5 6 7 8 9 |
cookies = {'key1': 'value1', 'key2': 'value2'} resp = requests.get('http://httpbin.org/cookies', cookies=cookies) print(resp.text) jar = requests.cookies.RequestsCookieJar() jar.set('tasty_cookie', 'yum', domain='httpbin.org', path='/cookies') jar.set('gross_cookie', 'blech', domain='httpbin.org', path='/elsewhere') resp = requests.get('http://httpbin.org/cookies', cookies=jar) print(resp.text) |
- 设置代理服务器。
1 2 3 4 |
requests.get('https://www.taobao.com', proxies={ 'http': 'http://10.10.1.10:3128', 'https': 'http://10.10.1.10:1080', }) |
说明:关于requests库的相关知识,还是强烈建议大家自行阅读它的官方文档。
- 设置请求超时。
1 |
requests.get('https://github.com', timeout=10) |
页面解析
几种解析方式的比较
参数 | 描述 |
---|---|
format | 必需。转换格式。 |
arg1 | 字符串中第一个 % 符号处的参数。 |
arg2 | 可选。规定插到 format 字符串中第二个 % 符号处的参数。 |
arg++ | 可选。规定插到 format 字符串中第三、四等等 % 符号处的参数。 |
说明:BeautifulSoup可选的解析器包括:Python标准库中的html.parser、lxml的HTML解析器、lxml的XML解析器和html5lib。
使用正则表达式解析页面
如果你对正则表达式没有任何的概念,那么推荐先阅读《正则表达式30分钟入门教程》,然后再阅读我们之前讲解在Python中如何使用正则表达式一文。
下面的例子演示了如何用正则表达式解析“豆瓣电影Top250”中的中文电影名称。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import random import re import time import requests PATTERN = re.compile(r'<a[^>]*?>\s*<span class="title">(.*?)</span>') for page in range(10): resp = requests.get( url=f'https://movie.douban.com/top250?start={page * 25}', headers={ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) ' 'AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/83.0.4103.97 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;' 'q=0.9,image/webp,image/apng,*/*;' 'q=0.8,application/signed-exchange;v=b3;q=0.9', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', }, ) items = PATTERN.findall(resp.text) for item in items: print(item) time.sleep(random.randint(1, 5)) |
XPath解析和lxml
XPath是在XML文档中查找信息的一种语法,它使用路径表达式来选取XML文档中的节点或者节点集。这里所说的XPath节点包括元素、属性、文本、命名空间、处理指令、注释、根节点等。
1 2 3 4 5 6 7 8 9 10 11 |
<?xml version="1.0" encoding="UTF-8"?> <bookstore> <book> <title lang="eng">Harry Potter</title> <price>29.99</price> </book> <book> <title lang="eng">Learning XML</title> <price>39.95</price> </book> </bookstore> |
对于上面的XML文件,我们可以用如下所示的XPath语法获取文档中的节点。
方法 | 说明 |
---|---|
util.timeAgo(time, onlyDate) 某个时间在当前时间的多久前。 | 参数 time:即为某个时间的时间戳或日期对象 参数 onlyDate:是否在超过30天后,只返回日期字符,而不返回时分秒 如果在3分钟以内,返回:刚刚 如果在30天以内,返回:若干分钟前、若干小时前、若干天前,如:5分钟前 如果在30天以上,返回:日期字符,如:2017-01-01 |
在使用XPath语法时,还可以使用XPath中的谓词。
包管理器名称 | 常用标志性指令 | 代表系统举例 |
---|---|---|
DPKG | dpkg、apt | Debain、Ubuntu等 |
RPM | rpm、yum | RedHat、CentOS等 |
Pacman | pacman | Arch、Manjaro等 |
DNF | dnf | Fedora |
Zypper | zypper | SUSE |
Portage | emerge | Gentoo |
XPath还支持通配符用法,如下所示。
值 | 描述 |
---|---|
auto | 默认值。如果必要则在元素前插入分页符。 |
always | 在元素前插入分页符。 |
avoid | 避免在元素前插入分页符。 |
left | 在元素之前足够的分页符,一直到一张空白的左页为止。 |
right | 在元素之前足够的分页符,一直到一张空白的右页为止。 |
inherit | 规定应该从父元素继承 page-break-before 属性的设置。 |
如果要选取多个节点,可以使用如下所示的方法。
[table “44” not found /]说明:上面的例子来自于菜鸟教程网站上XPath教程,有兴趣的读者可以自行阅读原文。
当然,如果不理解或者不太熟悉XPath语法,可以在Chrome浏览器中按照如下所示的方法查看元素的XPath语法。
下面的例子演示了如何用XPath解析“豆瓣电影Top250”中的中文电影名称。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
from lxml import etree import requests for page in range(10): resp = requests.get( url=f'https://movie.douban.com/top250?start={page * 25}', headers={ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) ' 'AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/83.0.4103.97 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;' 'q=0.9,image/webp,image/apng,*/*;' 'q=0.8,application/signed-exchange;v=b3;q=0.9', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', } ) html = etree.HTML(resp.text) spans = html.xpath('/html/body/div[3]/div[1]/div/div[1]/ol/li/div/div[2]/div[1]/a/span[1]') for span in spans: print(span.text) |
BeautifulSoup的使用
BeautifulSoup是一个可以从HTML或XML文件中提取数据的Python库。它能够通过你喜欢的转换器实现惯用的文档导航、查找、修改文档的方式。
- 遍历文档树
- 获取标签
- 获取标签属性
- 获取标签内容
- 获取子(孙)节点
- 获取父节点/祖先节点
- 获取兄弟节点
- 搜索树节点
- find / find_all
- select_one / select
说明:更多内容可以参考BeautifulSoup的官方文档。
下面的例子演示了如何用CSS选择器解析“豆瓣电影Top250”中的中文电影名称。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import random import time import bs4 import requests for page in range(10): resp = requests.get( url=f'https://movie.douban.com/top250?start={page * 25}', headers={ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) ' 'AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/83.0.4103.97 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;' 'q=0.9,image/webp,image/apng,*/*;' 'q=0.8,application/signed-exchange;v=b3;q=0.9', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', }, ) soup = bs4.BeautifulSoup(resp.text, 'lxml') elements = soup.select('.info>div>a') for element in elements: span = element.select_one('.title') print(span.text) time.sleep(random.random() * 5) |
例子 – 获取知乎发现上的问题链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import re from urllib.parse import urljoin import bs4 import requests def main(): headers = {'user-agent': 'Baiduspider'} base_url = 'https://www.zhihu.com/' resp = requests.get(urljoin(base_url, 'explore'), headers=headers) soup = bs4.BeautifulSoup(resp.text, 'lxml') href_regex = re.compile(r'^/question') links_set = set() for a_tag in soup.find_all('a', {'href': href_regex}): if 'href' in a_tag.attrs: href = a_tag.attrs['href'] full_url = urljoin(base_url, href) links_set.add(full_url) print('Total %d question pages found.' % len(links_set)) print(links_set) if __name__ == '__main__': main() |
本文来自这个系列长期转载Python-100-Days ,本文观点不代表蓝洛水深立场,转载请联系原作者。