来学爬虫吗?会坐牢的那种!

〇、整个公司被抓

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

出现了 403 Forbidden,访问拒绝。这是因为简书发现你可能是通过爬虫来访问页面了,所以会出现拒绝。怎么办呢?

我们可以使用伪装,就是伪装自己是浏览器,打开浏览器控制台,查看请求,可以在请求头中看到 user-agent 这一段:

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 来帮我们解析网页。

BeautifulSoup 是一个网页解析的工具,有了这个工具,就可以省去大量正则表达式,简化爬虫代码,快速获取我们需要的内容。还可以查看 Beautiful Soup 文档了解基本使用。

先安装 BeautifulSoup:

$ pip3 install beautifulsoup4

接着再装一个 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,))

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,110评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,443评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,474评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,881评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,902评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,698评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,418评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,332评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,796评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,968评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,110评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,792评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,455评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,003评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,130评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,348评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,047评论 2 355

推荐阅读更多精彩内容