就在昨天,豆酱的某乎在第二次15天禁言之后,又被有计划的禁言了15天,也就是说截止昨天(2020/3/27),豆酱因为之前的评论已经被禁言过7 15天,并将再加未来15天。可见这个并不是一时冲动的行为,而是有计划的。首先我相信某乎的公正性,也感谢做这个事情的人没对我的号做什么。 但是作为对豆酱昨天被禁言的回应,今天我的文章会调整一下。原计划是给大家科普自然语言处理(NLP)和文本分类,但今天我会先放出我B站视频展示的约600篇文章是如何爬取的技术文。
最后我想对那些不喜欢我的人说:我是个技术人,我开始尝试做内容是希望让大家展示好玩的技术,吸引大家学习知识。虽然我写的文章和视频引起了争议,但是我一直希望跟大家讲解的如何获得和爬取数据,如何分析,我的结论是如何获得的,希望大家喜欢这样探讨问题的方法。我虽然有自己的观点,我夫人也喜欢肖战,但我们并不想针对或死磕任何人。 你们正在使用你们所不齿的方法针对我们。 古话有云:己所不欲勿施于人。
文章无图有料,不懂技术的各位也应该仔细瞧瞧。
在之前的文章中,我已经和大家分享了如何直接爬取AO3的文章,那么如何找到文章的关联关系是一件比较头疼的问题。如果自己去写爬虫会比较浪费资源。最省事的方法就是借助搜索引擎进行资料获取。这里,我们就以 lofter 到 AO3 的外链为例。
加载的函数库还是与之前相同,这里我不再复述。
import sys
import re
import os
import time
from tqdm import tqdm
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from bs4 import BeautifulSoup
import random
这里和大家科普一下搜索引擎的高级搜索模式,平时大家使用搜索引擎,可能都是一整句话放进去搜索。但实际上搜索引擎是支持一定的高级语法以方便获取到更高级的内容。我们以谷歌为例:
“”精确匹配
使用引号来搜索一个完全匹配的字词或一组字词。在搜索歌词或文学作品中的一段文字时,此选项很实用。建议您只在查找非常确切的字词或词组时使用该功能,否则可能会无意中排除掉有用的搜索结果。
例如“见与不见” 搜索结果精确匹配“见与不见”,不能拆分成“见”与“不见”。
-排除字词
在某个字词前添加短横 (-) 可排除所有包含该字词的搜索结果。
例如:大熊猫 -百科 搜索结果中不出现“百科”
OR选择性字词搜索
选择性字词OR搜索结果匹配多个搜索字词中的任意一个。没有OR搜索结果中通常只会显示与多个字词都匹配的网页。
例如:奥运会 2014 OR 2018 搜索结果中会出现 “奥运会 2014”或者“奥运会 2018”的结果
site在特定网站或域名中搜索
在搜索中加入“site:”可以限定在某个特定网站中搜索信息
例如:LOFTER site:lofter.com
“site:”后面跟的站点域名,不要带“http://”。site:和站点名之间,不要带空格。
inurl在特定url链接中搜索
在搜索中加入“inurl:”可以限定在网站url链接中搜索网站信息
例如:auto视频教程 inurl:video
搜索词“auto视频教程”是可以出现在网页的任何位置,而“video”则必须出现在网页url中。
以上只是谷歌的部分高级搜索语法,百度也有类似的使用方法,大家可以自己去查查详细的使用方法。我们这里用到了 site: 标签 和 inurl: 标签 也就是:
site:lofter.com inurl:ao3
这条语句的含义是,在 lofter.com 中 搜索 含有 ao3 链接 的结果。这里需要注意,实际搜索过程中,"ao3" 需要换成该网站的实际域名。这里因为不想透露真实网站地址所以使用了 "ao3" 替代。
分析url 的思路我在 《我是怎样得到AO3内容的》 有介绍过,这里直接给结论。谷歌的url 由 search?后的内容构成:
- hl=en 表示搜索语言为英文
- q= 后跟搜索内容
- safe= 跟的是是否为安全搜索,这里使用images参数关闭安全搜索也就是可以搜索到不好的信息~
- num= 表示每页展示的搜索条数
- start= 表示从第几条开始显示,所以翻页的计算方法为 start = page*num
这里说明一下,我确实专门搜索了语言为英文的页面,但搜索引擎的模糊性使得结果依然有大部分是中文文章。但是我可以证明两点:
- 之前有说在ao3 看英文或学英语是真实的;
- 我还没有开始做文本分析,但就我看过的几篇英文文章中,以我留过学的经历来衡量,文章中确实含有书本上一般学不到的东西和词汇;【手动狗头】
言归正传看代码:
#获谷歌取搜索页面
def make_google_search_url(page=0, num=100):
base_loc = 'https://www.google.com/search?hl=en&q=site:lofter.com inurl:ao3&safe=images'
base_loc = "&num=" str(num)
base_loc = "&start=" str(page*num) #搜索页
return base_loc
获取链接的方法依然是 chrome 浏览器调试模式(F12)分析元素并用 BeautifulSoup 解析,这里不再复述,大家直接看代码。
#从谷歌获取文章链接
def get_url_from_search(html):
old_list = []
soup = BeautifulSoup(html, 'html.parser')
search_div = soup.find('div', attrs={'id': 'search'})
div_g_groups = search_div.findAll('div', attrs={'class': 'g'})
for g in div_g_groups:
div_r = g.find('div', attrs={'class': 'r'})
a_hurl = div_r.find('a')
old_list.append(a_hurl['href'])
return old_list
最后就是判断 lofter 的页面中是否含有 有效的 ao3 链接。按照之前的经验,判定含有 works 的 url 才考虑为有外链文章。但是在后来实践过程中 发现含有 users 的外链也非常有意思,就一并保存了。
保存的内容有: lofter 页面,本 lofter 页面中所有含有 ao3 外链的链接,所有涉及的 ao3 原文页面,ao3 用户介绍页(内含该用户所有文章)
注意,目前目前我只是保存了 ao3 用户介绍页(如果有)。并没有进行二次爬取或分析。
另外相比 《我是怎样得到AO3内容的》中的函数,这里进行了优化,当出现“Retry later”时,函数会自动重试,而不会想之前就直接把这一页放过不保存了。
代码中 ao3 站点地址我使用 xxx 代替。
def find_ao3_from_lofter(lofter_url_list, browser, path):
for url in lofter_url_list:
print(url)
dir_name = (
url.replace("http://", "")
.replace(".com/", "_")
.replace("/", "_")
.replace(".", "_")
)
dir_path = os.path.join(path, dir_name)
isExists = os.path.exists(dir_path)
if isExists:
print("Exists")
continue
# 判断结果
ao3_links = []
browser.get(url)
currurl = browser.current_url
if "xxx" in currurl and (
"/works/" in currurl or "/users/" in currurl
): # 如果url 直接跳转
ao3_links.append(currurl)
lhtml = ""
else: # 没有跳转
lhtml = browser.page_source
soup = BeautifulSoup(lhtml, "html.parser")
alink_groups = soup.findAll("a", attrs={"rel": "nofollow"})
for alink in alink_groups:
href_str = alink["href"]
if "xxx" in href_str and (
"/works/" in href_str or "/users/" in href_str
):
ao3_links.append(href_str)
if ao3_links:
# 判断路径是否存在
isExists = os.path.exists(dir_path)
# 如果不存在则创建目录
os.makedirs(dir_path)
links_str = url "\n"
need_agree = True
for work_url in ao3_links: # 遍历ao3链接
links_str = work_url "\n"
print(os.path.join(dir_path, "links.txt"))
fh = open(os.path.join(dir_path, "links.txt"), "w") # 保存页面
fh.write(links_str) # 写入内容
fh.close() # 关闭
print(os.path.join(dir_path, "lofter.html"))
fh = open(os.path.join(dir_path, "lofter.html"), "w") # 保存页面
fh.write(lhtml) # 写入内容
fh.close() # 关闭
for work_url in ao3_links:
browser.get(work_url)
if need_agree:
try:
time.sleep(3)
browser.find_element_by_id("tos_agree").click()
time.sleep(1)
browser.find_element_by_id("accept_tos").click()
time.sleep(1)
need_agree = False
except NoSuchElementException:
need_agree = False
work_html_text = browser.page_source # 获得页面代码
work_name = (
work_url.replace("https://", "")
.replace("http://", "")
.replace("xxx", "")
.replace(".com/", "")
.replace(".org/", "")
.replace("/", "_")
.replace(".", "_")
.replace("#", "_")
)
work_path = os.path.join(dir_path, work_name ".html")
if (
'If you accept cookies from our site and you choose "Proceed"'
in work_html_text
): # 无法获取正文则点击Proceed
browser.find_element_by_link_text("Proceed").click()
time.sleep(1)
browser.get(work_url)
work_html_text = browser.page_source
if "Retry later" in work_html_text:
while "Retry later" in work_html_text:
print(work_path)
fh = open(work_path, "w") # 保存页面
fh.write("Need_to_reload") # 写入内容
fh.close() # 关闭
print("Retry Later")
time.sleep(3)
browser.get("http://www.baidu.com")
time.sleep(3)
browser.quit()
c_service.stop()
time.sleep(60)
c_service.start()
browser = webdriver.Chrome(
chrome_options=chrome_options
) # 调用Chrome浏览器
browser.get("https://xxx.org/")
time.sleep(5)
browser.find_element_by_id("tos_agree").click()
time.sleep(2)
browser.find_element_by_id("accept_tos").click()
time.sleep(3)
browser.get(work_url)
work_html_text = browser.page_source # 获得页面代码
if (
'If you accept cookies from our site and you choose "Proceed"'
in work_html_text
): # 无法获取正文则点击Proceed
browser.find_element_by_link_text("Proceed").click()
time.sleep(1)
browser.get(work_url)
work_html_text = browser.page_source
# if "<!--chapter content-->" in work_html_text:
print(work_path)
fh = open(work_path, "w") # 保存页面
fh.write(work_html_text) # 写入内容
fh.close() # 关闭
time.sleep(float(random.randint(10, 50)) / 10) # 随机延时
return browser
设置起止页
start_p = 0
end_p = 4
如果平凡使用谷歌,谷歌会启动防机器人机制,这是函数会暂停等待我人工解锁的。
所以这里我也相当于解释了我为什么没有翻墙,因为如果我使用翻墙软件爬取,是会被谷歌发现并封*掉的,而如何绕过呢?卖个关子,看看有没有懂行的朋友帮大家解释一下。
c_service = webdriver.chrome.service.Service("/usr/bin/chromedriver")
c_service.command_line_args()
c_service.start()
chrome_options = webdriver.ChromeOptions()
# chrome_options.add_argument('--proxy-server=socks5://localhost:1080')
auto_quit_cnt = 0
browser = webdriver.Chrome(chrome_options=chrome_options) # 调用Chrome浏览器
for page in range(start_p, end_p):
print("-" * 30)
print("Page: " str(page))
print("-" * 30)
google_search_url = make_google_search_url(page)
browser.get(google_search_url)
html_text = browser.page_source # 获得页面代码
while "Our systems have detected unusual traffic" in html_text:
print("Google Robot!")
time.sleep(10)
html_text = browser.page_source # 获得页面代码
auto_quit_cnt = 1
if auto_quit_cnt > 30:
break
auto_quit_cnt = 0
lofter_list = get_url_from_search(html_text)
browser = find_ao3_from_lofter(lofter_list, browser, "lofter")
写在最后:
关于AO3这个系列,我还剩最后两篇文章:
这个话题做了快一个月了,我希望能够将我想讲的技术安安静静讲完。然后再带着大家探索其他有意思的编程技术,而不是揪着这个话题不放。
所以再次申明,我只是分析 AO3 其他事情我不做探讨和引申,也恳请大家理性思考和探讨。上文中我已经有限扩大了讨论范围。我的下一篇文章会按照我的规划来,我的下一个视频会是另一个好玩的技术。
我也希望即使你不喜欢我,也不要讨厌技术,不要讨厌学习。
在这段时间之前我是没有做Python数据分析的相关知识的;虽然同属深度学习,NLP不是我的专业,所以我也是第一次实践,但是通过这个热点,我收获了很多新知识,也有很多人给我点赞鼓励交流探讨。我收获了很多。
但是,
你收获了什么呢?