Skip to content

Commit e86dece

Browse files
committed
更新了爬虫第1天的代码和文档
1 parent a97f4ac commit e86dece

File tree

3 files changed

+189
-49
lines changed

3 files changed

+189
-49
lines changed

Day66-75/01.网络爬虫和相关工具.md

+129-4
Original file line numberDiff line numberDiff line change
@@ -173,10 +173,135 @@ HTTP响应(响应行+响应头+空行+消息体):
173173

174174
### 一个简单的爬虫
175175

176-
构造一个爬虫一般分为数据采集、数据处理和数据存储三个部分的内容。
176+
一个基本的爬虫通常分为数据采集(网页下载)、数据处理(网页解析)和数据存储(将有用的信息持久化)三个部分的内容,当然更为高级的爬虫在数据采集和处理时会使用并发编程或分布式技术,其中可能还包括调度器和后台管理程序(监控爬虫的工作状态以及检查数据抓取的结果)。
177+
178+
![](./res/crawler-workflow.png)
179+
180+
1. 设定抓取目标(种子页面)并获取网页。
181+
2. 当服务器无法访问时,设置重试次数。
182+
3. 在需要的时候设置用户代理(否则无法访问页面)。
183+
4. 对获取的页面进行必要的解码操作。
184+
5. 通过正则表达式获取页面中的链接。
185+
6. 对链接进行进一步的处理(获取页面并重复上面的动作)。
186+
7. 将有用的信息进行持久化(以备后续的处理)。
187+
188+
```Python
189+
from urllib.error import URLError
190+
from urllib.request import urlopen
191+
192+
import re
193+
import pymysql
194+
import ssl
195+
196+
from pymysql import Error
197+
198+
199+
# 通过指定的字符集对页面进行解码(不是每个网站都将字符集设置为utf-8)
200+
def decode_page(page_bytes, charsets=('utf-8',)):
201+
page_html = None
202+
for charset in charsets:
203+
try:
204+
page_html = page_bytes.decode(charset)
205+
break
206+
except UnicodeDecodeError:
207+
pass
208+
# logging.error('Decode:', error)
209+
return page_html
210+
211+
212+
# 获取页面的HTML代码(通过递归实现指定次数的重试操作)
213+
def get_page_html(seed_url, *, retry_times=3, charsets=('utf-8',)):
214+
page_html = None
215+
try:
216+
page_html = decode_page(urlopen(seed_url).read(), charsets)
217+
except URLError:
218+
# logging.error('URL:', error)
219+
if retry_times > 0:
220+
return get_page_html(seed_url, retry_times=retry_times - 1,
221+
charsets=charsets)
222+
return page_html
223+
224+
225+
# 从页面中提取需要的部分(通常是链接也可以通过正则表达式进行指定)
226+
def get_matched_parts(page_html, pattern_str, pattern_ignore_case=re.I):
227+
pattern_regex = re.compile(pattern_str, pattern_ignore_case)
228+
return pattern_regex.findall(page_html) if page_html else []
229+
230+
231+
# 开始执行爬虫程序并对指定的数据进行持久化操作
232+
def start_crawl(seed_url, match_pattern, *, max_depth=-1):
233+
conn = pymysql.connect(host='localhost', port=3306,
234+
database='crawler', user='root',
235+
password='123456', charset='utf8')
236+
try:
237+
with conn.cursor() as cursor:
238+
url_list = [seed_url]
239+
# 通过下面的字典避免重复抓取并控制抓取深度
240+
visited_url_list = {seed_url: 0}
241+
while url_list:
242+
current_url = url_list.pop(0)
243+
depth = visited_url_list[current_url]
244+
if depth != max_depth:
245+
page_html = get_page_html(current_url, charsets=('utf-8', 'gbk', 'gb2312'))
246+
links_list = get_matched_parts(page_html, match_pattern)
247+
param_list = []
248+
for link in links_list:
249+
if link not in visited_url_list:
250+
visited_url_list[link] = depth + 1
251+
page_html = get_page_html(link, charsets=('utf-8', 'gbk', 'gb2312'))
252+
headings = get_matched_parts(page_html, r'<h1>(.*)<span')
253+
if headings:
254+
param_list.append((headings[0], link))
255+
cursor.executemany('insert into tb_result values (default, %s, %s)',
256+
param_list)
257+
conn.commit()
258+
except Error:
259+
pass
260+
# logging.error('SQL:', error)
261+
finally:
262+
conn.close()
263+
264+
265+
def main():
266+
ssl._create_default_https_context = ssl._create_unverified_context
267+
start_crawl('http://sports.sohu.com/nba_a.shtml',
268+
r'<a[^>]+test=a\s[^>]*href=["\'](.*?)["\']',
269+
max_depth=2)
270+
271+
272+
if __name__ == '__main__':
273+
main()
177274

178-
首先我们要设定抓取的目标并获取网页。
275+
```
276+
277+
注意事项:
278+
279+
1. 处理相对链接。有的时候我们从页面中获取的链接不是一个完整的绝对链接而是一个相对链接,这种情况下需要将其与URL前缀进行拼接(urllib.parse中的urljoin函数可以完成此项操作)。
280+
281+
2. 设置代理服务。有些网站会限制访问的区域(例如美国的Netflix屏蔽了很多国家的访问),有些爬虫需要隐藏自己的身份,在这种情况下可以设置代理服务器(urllib.request中的ProxyHandler就是用来进行此项操作)。
282+
283+
3. 限制下载速度。如果我们的爬虫获取网页的速度过快,可能就会面临被封禁或者产生“损害动产”的风险(这个可能会导致吃官司且败诉哦),可以在两次下载之间添加延时从而对爬虫进行限速。
284+
285+
4. 避免爬虫陷阱。有些网站会动态生成页面内容,这会导致产生无限多的页面(例如在线万年历等)。可以通过记录到达当前页面经过了多少个链接(链接深度)来解决该问题,当达到事先设定的最大深度时爬虫就不再像队列中添加该网页中的链接了。
286+
287+
5. SSL相关问题。在使用`urlopen`打开一个HTTPS链接时会验证一次SSL证书,如果不做出处理会产生错误提示“SSL: CERTIFICATE_VERIFY_FAILED”,可以通过以下两种方式加以解决:
288+
289+
- 使用未经验证的上下文
290+
291+
```Python
292+
import ssl
293+
294+
request = urllib.request.Request(url='...', headers={...})
295+
context = ssl._create_unverified_context()
296+
web_page = urllib.request.urlopen(request, context=context)
297+
```
298+
299+
- 设置全局的取消证书验证
179300

180-
1. 设置重试次数。
181-
2. 设置用户代理。
301+
```Python
302+
import ssl
303+
304+
ssl._create_default_https_context = ssl._create_unverified_context
305+
```
182306

307+

Day66-75/code/example01.py

+60-45
Original file line numberDiff line numberDiff line change
@@ -3,58 +3,73 @@
33

44
import re
55
import pymysql
6+
import ssl
67

8+
from pymysql import Error
79

8-
def get_page_code(start_url, *, retry_times=3, charsets=('utf-8', )):
10+
11+
def decode_page(page_bytes, charsets=('utf-8', )):
12+
page_html = None
13+
for charset in charsets:
14+
try:
15+
page_html = page_bytes.decode(charset)
16+
break
17+
except UnicodeDecodeError:
18+
pass
19+
# logging.error('Decode:', error)
20+
return page_html
21+
22+
23+
def get_page_html(seed_url, *, retry_times=3, charsets=('utf-8', )):
24+
page_html = None
925
try:
10-
for charset in charsets:
11-
try:
12-
html = urlopen(start_url).read().decode(charset)
13-
break
14-
except UnicodeDecodeError:
15-
html = None
16-
except URLError as ex:
17-
print('Error:', ex)
18-
return get_page_code(start_url, retry_times=retry_times - 1, charsets=charsets) if \
19-
retry_times > 0 else None
20-
return html
26+
page_html = decode_page(urlopen(seed_url).read(), charsets)
27+
except URLError:
28+
# logging.error('URL:', error)
29+
if retry_times > 0:
30+
return get_page_html(seed_url, retry_times=retry_times - 1,
31+
charsets=charsets)
32+
return page_html
33+
34+
35+
def get_matched_parts(page_html, pattern_str, pattern_ignore_case=re.I):
36+
pattern_regex = re.compile(pattern_str, pattern_ignore_case)
37+
return pattern_regex.findall(page_html) if page_html else []
38+
39+
40+
def start_crawl(seed_url, match_pattern):
41+
conn = pymysql.connect(host='localhost', port=3306,
42+
database='crawler', user='root',
43+
password='123456', charset='utf8')
44+
try:
45+
with conn.cursor() as cursor:
46+
url_list = [seed_url]
47+
while url_list:
48+
current_url = url_list.pop(0)
49+
page_html = get_page_html(current_url, charsets=('utf-8', 'gbk', 'gb2312'))
50+
links_list = get_matched_parts(page_html, match_pattern)
51+
url_list += links_list
52+
param_list = []
53+
for link in links_list:
54+
page_html = get_page_html(link, charsets=('utf-8', 'gbk', 'gb2312'))
55+
headings = get_matched_parts(page_html, r'<h1>(.*)<span')
56+
if headings:
57+
param_list.append((headings[0], link))
58+
cursor.executemany('insert into tb_result values (default, %s, %s)',
59+
param_list)
60+
conn.commit()
61+
except Error:
62+
pass
63+
# logging.error('SQL:', error)
64+
finally:
65+
conn.close()
2166

2267

2368
def main():
24-
url_list = ['http://sports.sohu.com/nba_a.shtml']
25-
visited_list = set({})
26-
while len(url_list) > 0:
27-
current_url = url_list.pop(0)
28-
visited_list.add(current_url)
29-
print(current_url)
30-
html = get_page_code(current_url, charsets=('utf-8', 'gbk', 'gb2312'))
31-
if html:
32-
link_regex = re.compile(r'<a[^>]+href=["\'](.*?)["\']', re.IGNORECASE)
33-
link_list = re.findall(link_regex, html)
34-
url_list += link_list
35-
conn = pymysql.connect(host='localhost', port=3306,
36-
db='crawler', user='root',
37-
passwd='123456', charset='utf8')
38-
try:
39-
for link in link_list:
40-
if link not in visited_list:
41-
visited_list.add(link)
42-
print(link)
43-
html = get_page_code(link, charsets=('utf-8', 'gbk', 'gb2312'))
44-
if html:
45-
title_regex = re.compile(r'<h1>(.*)<span', re.IGNORECASE)
46-
match_list = title_regex.findall(html)
47-
if len(match_list) > 0:
48-
title = match_list[0]
49-
with conn.cursor() as cursor:
50-
cursor.execute('insert into tb_result (rtitle, rurl) values (%s, %s)',
51-
(title, link))
52-
conn.commit()
53-
finally:
54-
conn.close()
55-
print('执行完成!')
69+
ssl._create_default_https_context = ssl._create_unverified_context
70+
start_crawl('http://sports.sohu.com/nba_a.shtml',
71+
r'<a[^>]+test=a\s[^>]*href=["\'](.*?)["\']')
5672

5773

5874
if __name__ == '__main__':
5975
main()
60-

Day66-75/res/crawler-workflow.png

74.1 KB
Loading

0 commit comments

Comments
 (0)