Skip to content

Commit 8c49c81

Browse files
committed
fix: 改进被封禁账号爬取及添加代理支持
1. 添加全局代理配置支持(config.json 中配置 proxy 字段) 2. handle_html 增加 403/432 等状态码的差异化处理和重试策略 3. info_parser 兼容自己查看自己资料页和查看他人资料页两种HTML结构 4. 学习经历/工作经历提取改用 following-sibling 定位,更健壮 5. page_parser 异常时返回空列表而非 None,避免上层解包报错 6. 文件下载和视频URL获取均支持代理
1 parent c1d43e8 commit 8c49c81

5 files changed

Lines changed: 113 additions & 47 deletions

File tree

weibo_spider/downloader/downloader.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@ def download_one_file(self, url, file_path, weibo_id):
3636
s = requests.Session()
3737
s.mount(url,
3838
HTTPAdapter(max_retries=self.file_download_timeout[0]))
39+
from ..parser.util import get_proxies
3940
downloaded = s.get(url,
4041
timeout=(self.file_download_timeout[1],
41-
self.file_download_timeout[2]))
42+
self.file_download_timeout[2]),
43+
proxies=get_proxies())
4244
with open(file_path, 'wb') as f:
4345
f.write(downloaded.content)
4446
except Exception as e:

weibo_spider/parser/info_parser.py

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,31 +25,64 @@ def extract_user_info(self):
2525
sys.exit()
2626
user.nickname = nickname
2727

28-
basic_info = self.selector.xpath("//div[@class='c'][3]/text()")
2928
zh_list = [u'性别', u'地区', u'生日', u'简介', u'认证', u'达人']
3029
en_list = [
3130
'gender', 'location', 'birthday', 'description',
3231
'verified_reason', 'talent'
3332
]
33+
34+
# 先尝试标准格式(查看他人资料页)
35+
basic_info = self.selector.xpath("//div[@class='c'][3]/text()")
36+
has_info = any(
37+
':' in str(i) and str(i).split(':', 1)[0] in zh_list
38+
for i in basic_info)
39+
40+
if not has_info:
41+
# 自己查看自己的资料页:标签在<a>标签内,值在<a>的tail文本中
42+
basic_info = []
43+
for c_div in self.selector.xpath("//div[@class='c']"):
44+
a_texts = c_div.xpath('a/text()')
45+
if u'性别' in a_texts or u'昵称' in a_texts:
46+
for a in c_div.xpath('a'):
47+
label = (a.text or '').strip()
48+
tail = (a.tail or '').strip()
49+
if label in zh_list and tail.startswith(':'):
50+
basic_info.append(label + tail)
51+
break
52+
3453
for i in basic_info:
35-
if i.split(':', 1)[0] in zh_list:
36-
setattr(user, en_list[zh_list.index(i.split(':', 1)[0])],
37-
i.split(':', 1)[1].replace('\u3000', ''))
38-
39-
experienced = self.selector.xpath("//div[@class='tip'][2]/text()")
40-
if experienced and experienced[0] == u'学习经历':
41-
user.education = self.selector.xpath(
42-
"//div[@class='c'][4]/text()")[0][1:].replace(
43-
u'\xa0', u' ')
44-
if self.selector.xpath(
45-
"//div[@class='tip'][3]/text()")[0] == u'工作经历':
46-
user.work = self.selector.xpath(
47-
"//div[@class='c'][5]/text()")[0][1:].replace(
48-
u'\xa0', u' ')
49-
elif experienced and experienced[0] == u'工作经历':
50-
user.work = self.selector.xpath(
51-
"//div[@class='c'][4]/text()")[0][1:].replace(
52-
u'\xa0', u' ')
54+
if ':' in str(i) and str(i).split(':', 1)[0] in zh_list:
55+
setattr(user, en_list[zh_list.index(str(i).split(':', 1)[0])],
56+
str(i).split(':', 1)[1].replace('\u3000', ''))
57+
58+
# 提取学习经历和工作经历,使用following-sibling定位,兼容自己和他人页面
59+
tip_divs = self.selector.xpath("//div[@class='tip']")
60+
for tip in tip_divs:
61+
tip_text = tip.xpath('string(.)').strip()
62+
if tip_text == u'学习经历':
63+
edu_div = tip.xpath(
64+
'following-sibling::div[@class="c"][1]')
65+
if edu_div:
66+
# 优先用text()(他人页面),fallback用string(.)(自己页面)
67+
edu_text = edu_div[0].xpath('text()')
68+
if edu_text and len(edu_text[0].strip()) > 1:
69+
user.education = edu_text[0][1:].replace(
70+
u'\xa0', u' ')
71+
else:
72+
user.education = ' '.join(
73+
edu_div[0].xpath('string(.)').split())
74+
elif tip_text == u'工作经历':
75+
work_div = tip.xpath(
76+
'following-sibling::div[@class="c"][1]')
77+
if work_div:
78+
work_text = work_div[0].xpath('text()')
79+
if work_text and len(work_text[0].strip()) > 1:
80+
user.work = work_text[0][1:].replace(
81+
u'\xa0', u' ')
82+
else:
83+
user.work = ' '.join(
84+
work_div[0].xpath('string(.)').split())
85+
5386
return user
5487
except Exception as e:
5588
logger.exception(e)

weibo_spider/parser/page_parser.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ def get_one_page(self, weibo_id_list):
9191
return weibos, weibo_id_list, self.to_continue
9292
except Exception as e:
9393
logger.exception(e)
94+
return [], weibo_id_list, self.to_continue
9495

9596
def is_original(self, info):
9697
"""判断微博是否为原创微博"""

weibo_spider/parser/util.py

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,37 +12,62 @@
1212
URL_MAP_FILE = 'url_map.json'
1313
logger = logging.getLogger('spider.util')
1414

15+
# 全局代理配置,由 spider.py 初始化
16+
_proxies = None
17+
18+
19+
def set_proxies(proxy_url):
20+
"""设置全局代理"""
21+
global _proxies
22+
if proxy_url:
23+
_proxies = {'http': proxy_url, 'https': proxy_url}
24+
logger.info(u'已启用代理: %s', proxy_url)
25+
26+
27+
def get_proxies():
28+
return _proxies
29+
1530

1631
def hash_url(url):
1732
return hashlib.sha224(url.encode('utf8')).hexdigest()
1833

1934

35+
DEFAULT_UA = ('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) '
36+
'AppleWebKit/537.36 (KHTML, like Gecko) '
37+
'Chrome/133.0.0.0 Safari/537.36')
38+
39+
2040
def handle_html(cookie, url):
2141
"""处理html"""
22-
try:
23-
user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36'
24-
headers = {'User_Agent': user_agent, 'Cookie': cookie}
25-
resp = requests.get(url, headers=headers)
26-
27-
if GENERATE_TEST_DATA:
28-
import io
29-
import os
30-
31-
resp_file = os.path.join(TEST_DATA_DIR, '%s.html' % hash_url(url))
32-
with io.open(resp_file, 'w', encoding='utf-8') as f:
33-
f.write(resp.text)
34-
35-
with io.open(os.path.join(TEST_DATA_DIR, URL_MAP_FILE), 'r+') as f:
36-
url_map = json.loads(f.read())
37-
url_map[url] = resp_file
38-
f.seek(0)
39-
f.write(json.dumps(url_map, indent=4, ensure_ascii=False))
40-
f.truncate()
41-
42-
selector = etree.HTML(resp.content)
43-
return selector
44-
except Exception as e:
45-
logger.exception(e)
42+
from time import sleep
43+
headers = {'User-Agent': DEFAULT_UA, 'Cookie': cookie}
44+
for attempt in range(5):
45+
try:
46+
resp = requests.get(url, headers=headers, timeout=10,
47+
proxies=_proxies)
48+
if resp.status_code == 200 and len(resp.content) > 0:
49+
selector = etree.HTML(resp.content)
50+
return selector
51+
elif resp.status_code == 403:
52+
wait = 300 * (attempt + 1)
53+
logger.warning(u'403 IP被限制,等待%d秒后重试(第%d次)',
54+
wait, attempt + 1)
55+
sleep(wait)
56+
elif resp.status_code == 432:
57+
logger.error(u'432 User-Agent被拒绝,请更新UA')
58+
return None
59+
else:
60+
wait = 60 * (attempt + 1)
61+
logger.warning(u'请求返回状态码%d,等待%d秒后重试(第%d次)',
62+
resp.status_code, wait, attempt + 1)
63+
sleep(wait)
64+
except Exception as e:
65+
wait = 60 * (attempt + 1)
66+
logger.warning(u'请求异常,等待%d秒后重试(第%d次): %s',
67+
wait, attempt + 1, str(e))
68+
sleep(wait)
69+
logger.error(u'请求%s失败,已重试5次', url)
70+
return None
4671

4772

4873
def handle_garbled(info):
@@ -95,9 +120,9 @@ def to_video_download_url(cookie, video_page_url):
95120
video_object_url = video_page_url.replace('m.weibo.cn/s/video/show',
96121
'm.weibo.cn/s/video/object')
97122
try:
98-
user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36'
99-
headers = {'User_Agent': user_agent, 'Cookie': cookie}
100-
wb_info = requests.get(video_object_url, headers=headers).json()
123+
headers = {'User-Agent': DEFAULT_UA, 'Cookie': cookie}
124+
wb_info = requests.get(video_object_url, headers=headers,
125+
proxies=_proxies).json()
101126
video_url = wb_info['data']['object']['stream'].get('hd_url')
102127
if not video_url:
103128
video_url = wb_info['data']['object']['stream']['url']

weibo_spider/spider.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,11 @@ def main(_):
385385
try:
386386
config = _get_config()
387387
config_util.validate_config(config)
388+
# 初始化代理
389+
proxy = config.get('proxy')
390+
if proxy:
391+
from .parser.util import set_proxies
392+
set_proxies(proxy)
388393
wb = Spider(config)
389394
wb.start() # 爬取微博信息
390395
except Exception as e:

0 commit comments

Comments
 (0)