〇、整个公司被抓
2019 年的某一个工作日,公司员工像往常一样忙忙碌碌,某个程序员和产品经理正在为了一个需求争吵,小明带着耳机正坐在办公室敲代码。
突然就来了一大群警察,要求所有人离开工位,双手离开电脑、手机等设备。整个公司的人都懵了,不知道发生了什么事情,但也都照办了。
警察很快查封了公司的所有办公用品,问技术部相关人员要了服务器的信息,公司全体上下 200 多人无差别的全部送到看守所了解情况。
在去看守所的路上,大家都还心里想这是不是搞错了,我们只是一个科技公司公司又没有骗人,怎么就集体被抓了。
小明也一直认为自己没有犯罪,自己只是一名技术人员而已,所有的工作也都是按照领导要求来执行的,应该很快就会把我们释放了吧。
随后,公司非核心人员都被释放了出来,主要集中在 HR、行政人员。最后确认公司 36 人被捕,其中大部分是程序员。
被捕后小明委托的律师事务所,就是和我们交流的两位律师的事务所,据说小明入狱后就一直不认为自己有罪,也因一直拒绝认罪从而错过了取保候审的机会。
目前小明还在等待最后的审判……
好了,下面我们开始学 坐牢 爬虫。
一、什么是爬虫
其实你身边到处都是爬虫的产物,比如说搜索引擎 Google、百度,它们能为你提供这么多搜索结果,也都是因为它们或取了很多网页信息,然后展示给你。再来说一些商业爬虫,比如爬取淘宝的同类商品的价格信息,好为自己的商品挑选合适的价格。爬虫的用途很多很多,网上的信息成百上千,只要你懂爬虫,你都能轻松获取。
二、爬取网页
这里使用 Python3 来做爬虫,首先你得下载 Python3,如果你还没有安装可以看一看《Linux 安装 Python 3.x》。
然后还需要安装 Requests,这是一个 Python 的常用的外部模块,我们需要手动安装它。简单的方法,使用 pip 安装就好了。
$ pip3 install requests
现在我们已经可以开始爬取网页了。例如,我们爬取百度百科
import requests
URL = 'https://baike.baidu.com/item/%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB'
# 使用 requests 发起 Get 请求获取网页数据
request = requests.get(URL)
# 使用 utf-8 编码,避免乱码
request.encoding = 'utf-8'
# 获取请求文本
html = request.text
print(html)
经过这么几行代码,我们已经把百度百科的一个网页的信息全部爬取下来了。
好,我们换一个 URL 爬取试试,比如说简书:https://www.jianshu.com/u/5fa5459c7b02
。这时你会发现出问题了。
出现了 403 Forbidden,访问拒绝。这是因为简书发现你可能是通过爬虫来访问页面了,所以会出现拒绝。怎么办呢?
我们可以使用伪装,就是伪装自己是浏览器,打开浏览器控制台,查看请求,可以在请求头中看到 user-agent
这一段:
我们也仿照浏览器的请求,也就是在请求头中加入 user-agent
,完整代码如下:
import requests
URL = 'https://www.jianshu.com/u/5fa5459c7b02'
# 请求头
headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36'}
# 使用 requests 发起 Get 请求获取网页数据
request = requests.get(URL, headers=headers)
# 使用 utf-8 编码,避免乱码
request.encoding = 'utf-8'
# 获取请求文本
html = request.text
print(html)
现在可以成功爬取了。
加入请求头只是一种简单的破解反爬技术,实际可能会遇到其他反爬取技术,这就需要具体问题具体分析了,比如请求带 cookie
等。当然加请求头的方法已经可以成功爬取大部分网页了。
目前很多网站都增加了频率限制,一个 IP 在一定的时间内访问次数有限,最好不要去破解,一旦你的爬取频率过高,导致对方服务器瘫痪,这就是网络攻击了。
三、获取需要的信息
虽然把网页爬取下来了,但是有很多信息是我们不需要的。例如,我只想看看网页的标题,却把整个网页都爬取下来了。那怎么从整个网页中获取我们需要的内容呢?
先安装 BeautifulSoup:
$ pip3 install beautifulsoup4
BeautifulSoup 是一个网页解析的工具,有了这个工具,就可以省去大量正则表达式,简化爬虫代码,快速获取我们需要的内容。还可以查看 Beautiful Soup 文档了解基本使用。
接着再装一个 HTML 解析器就好了:
$ pip3 install lxml
准备完毕,可以开始了。例如我需要获取网页的标题,获取 <p> 标签,获取 <a> 标签的连接等。
from bs4 import BeautifulSoup
import requests
URL = 'https://www.jianshu.com/u/5fa5459c7b02'
headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36'}
# 使用 requests 发起 Get 请求获取网页数据
request = requests.get(URL, headers=headers)
request.encoding = 'utf-8'
html = request.text
# 使用 BeautifulSoup 解析网页
soup = BeautifulSoup(html, 'lxml')
# 获取网页 title
title = soup.title.string
# 获取 <p> 标签
p = soup.find_all('p')
# 获取 <a> 标签
a = soup.find_all('a')
# 获取 <a> 标签的链接
# 因为 <a> 真正的 link 在 <a href="link"> 里面,href 也可以看做是 <a> 的一个属性,用字典的形式来读取
a_href = [h['href'] for h in a]
# 获取 class 为 'have-img' 的 <li> 标签
img_li = soup.find_all('li', {'class': 'have-img'})
# 获取 <li> 标签里面的 <img> 标签
for li in img_li:
imgs = li.find_all('img')
到这里,已经小功告成,爬虫的基本使用已经介绍完毕,如果想爬取更符合自定义标准的信息,那么还得需要了解正则表达式才行。
四、正则表达式
正则表达式 (Regular Expression) 又称 RegEx,是用来匹配字符的一种工具。在一大串字符中寻找你需要的内容。它常被用在很多方面,比如网页爬虫,文稿整理,数据筛选等等。最简单的一个例子:
<td>
<img src="https://xxx/1.jpg">
<img src="http://xxx/2.jpg">
<img src="ftp://xxx/3.png">
</td>
我们只需要提取以 https 开头的 url,这该如何处理呢?这是我们就需要用到正则表达式了。
正则表达式的大致匹配过程是:依次拿出表达式和文本中的字符比较,如果每一个字符都能匹配,则匹配成功;一旦有匹配不成功的字符则匹配失败。下面列出了正则表达式元字符和语法:
-
\d
:任何数字,[0-9] -
\D
:不是数字,[^\d] -
\s
:任何空白字符, 如 [\t \n \r <空格>] -
\S
:不是空白字符 -
\w
:任何大小写字母、数字,[a-zA-Z0-9] -
\W
:不是字母、数字,[^\w] -
.
:匹配任何字符 (除了 \n) -
^
:匹配开头 -
$
:匹配结尾 -
?
:匹配前一个字符0次或1次 -
*
:匹配前一个字符0次或多次 -
+
:匹配前一个字符1次或多次
下面就是具体的举例说明:
import re # 导入正则匹配模块
# 匹配前一个字符1次或多次
re.search(r"Mon(day)+", "Monday") # <re.Match object; span=(0, 6), match='Monday'>
re.search(r"Mon(day)+", "Mon") # None
re.search(r"Mon(day)*", "Mon") # <re.Match object; span=(0, 3), match='Mon'>
# 匹配数字
re.compile('\d').match('abc123') # <re.Match object; span=(3, 4), match='1'>
re.compile('\d+').match('abc123') # <re.Match object; span=(3, 6), match='123'>
# 匹配字母和数字
re.compile('\w').match('abc123') # <re.Match object; span=(0, 1), match='a'>
re.compile('\w+').match('abc123') # <re.Match object; span=(0, 6), match='abc123'>
# 匹配开头
re.compile('^abc').match('abc123') # <re.Match object; span=(0, 3), match='abc'>
# 匹配结尾
re.compile('123$').match('abc123') # <re.Match object; span=(3, 6), match='123'>
通过这些简单的了解,我们就能知道以 https:// 开头的正则表达式应该怎么写了:
re.compile('^(https://).+')
其中 ^(https://)
表示以 https:// 开头,.
匹配任意字符,+
表示匹配任意次数,合并起来表示匹配以 https:// 开头,之后可以是任意字符出现任意次。完整代码如下:
from bs4 import BeautifulSoup
import requests
import re # 导入正则匹配模块
# 获取网页
html = requests.get(URL).text
# 使用 BeautifulSoup 解析网页
soup = BeautifulSoup(html, 'lxml')
# 获取 <img> 标签,并且 src 以 https:// 开头
imgs = soup.find_all('img', {'src': re.compile('^(https://).*')})
print(imge) # <img src="https://xxx/1.jpg">
print(imge['src']) # https://xxx/1.jpg
如果需要获取以 png 结尾的图片呢?只需要改一句话:
img = soup.find_all('img', {'src': re.compile('.+(.png)$')})
五、图片下载
现在我们来做一个小练习,就是爬取 国家地理中文网 中一个页面上的所有图片,并且下载到本地。
查看网页可以看到,图片是 <img> 标签,并且连接是以 http 开头,通过这样简单的分析,就可以开始下载图片了。
由于网页可能会更新,代码并不是一成不变的,重点是了解下载方法,同时也可以根据自己的需求去下载。
from bs4 import BeautifulSoup
import requests
import re
import ssl
import urllib
# 解决访问 HTTPS 时不受信任 SSL 证书问题
# ssl._create_default_https_context = ssl._create_unverified_context
URL = "http://www.nationalgeographic.com.cn/animals"
headers = {'User-Agent': 'Mozilla/5.0 3578.98 Safari/537.36'}
request = requests.get(URL, headers=headers)
request.encoding = 'utf-8'
html = request.text
soup = BeautifulSoup(html, 'lxml')
# 获取 'http://' 或 'https://' 开头的 <img> 标签
img_urls = soup.find_all('img', {'src': re.compile('^(http(s)?://).+')})
for i in range(len(img_urls)):
url = img_urls[i]['src']
# 下载图片
urllib.request.urlretrieve(url, '/Users/Desktop/Picture/' + str(i) + '.jpg')
图片已经下载完成
下载图片现在可以爬取一个网页上的图片了,如果我想把整个 国家地理中文网 上所有的图片都下载呢?
import time
from bs4 import BeautifulSoup
import re
import requests
import urllib
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
base_url = 'http://www.ngchina.com.cn/animals/'
headers = {'User-Agent': 'Mozilla/5.0 3578.98 Safari/537.36'}
def crawl(url):
'''
爬取网页
:param url: url
:return: url and current web content
'''
request = requests.get(url, headers=headers)
request.encoding = 'utf-8'
html = request.text
return url, html
def parse(url, html):
'''
解析网页
'''
soup = BeautifulSoup(html, 'lxml')
title = soup.title.string # 获取网页标题
# 获取当前页面上的链接,待下个循环爬取,只拿国家地理中文网的链接
urls = soup.find_all('a', {"href": re.compile('^(http://www.ngchina).+')})
page_urls = set([url['href'] for url in urls]) # 链接去重
download_img(soup) # 下载图片
return title, url, page_urls
def download_img(soup):
"""
下载以 http 或 https 开头的图片
"""
img_urls = soup.find_all('img', {'src': re.compile('^(http(s)?://).+')})
for i in range(len(img_urls)):
url = img_urls[i]['src']
urllib.request.urlretrieve(url, '/Users/terry-jri/Desktop/Picture/' + str(time.time()) + '.jpg')
# 待爬取网页url
unseen = set([base_url, ])
# 已爬取网页url
seen = set()
count, t1 = 1, time.time()
while len(unseen) != 0: # still get some url to visit
if len(seen) > 100: # max count
break
print('\nCrawling...')
htmls = [crawl(url) for url in unseen]
print('\nParsing...')
results = [parse(url, html) for url, html in htmls]
print('\nAnalysing...')
seen.update(unseen) # seen the crawled
unseen.clear() # nothing unseen
for title, url, page_urls in results:
print(count, title, url)
count += 1
unseen.update(page_urls - seen) # get new url to crawl
print('Total time: %.1f s' % (time.time() - t1,))
网友评论