scrapy 开始做项目
1, 安装:
pip3 install scrapy
scrapy依赖的包也会一并安装,不需要额外再安装什么东西了。
建议在virtualfish下安装,这样可以管理版本。
2, 新建项目:
scrapy startproject craigslist
会在当前目录为你创建一个craigslist project, craigslist是目录名称,也就是说是整个project的框架。目录结构如下:
├── scrapy.cfg
└── craigslist
├── __init__.py
├── items.py
├── pipelines.py
├── settings.py
└── spiders
└── __init__.py
这个目录里的spiders目录才是真的蜘蛛,一个项目可以包含多个蜘蛛。
进入spiders目录,新建一个spider。我们可以自己手动建一个文件,也可以让scrapy帮我创建。
若让scrapy帮我们创建,则先进入spiders目录:
scrapy genspider realestate newyork.craigslist.org/d/real-estate/search/rea
它会为我们自动创建一个名为 realestate.py的文件,这就是我们的一个蜘蛛。
3, 运行:
运行一个spider的方法:
scrapy crawl realestate
4, 调试
4.1, Scrapy Shell
使用 inspect_response 可以在代码里将scrapy shell 调出来,在执行到某行的时候,直接进入命令行进行调试。scrapy shell 可以用来试探性打印xpath的结果,不需要重复运行爬虫,也不需要import相应的模块, 对于抓取元素定位很有帮助。对于动态网页的分析,需要将这个网页存为文件,然后用scrapy shell来分析。scrapy shell 默认使用ipython 命令行(必须安装ipython,对于使用virtualenv或virtualfish,必须在虚拟环境执行 pip install ipython来安装,不然系统环境/usr/bin/python的ipython是没法得到调用的)。调试命令:
scrapy shell -L INFO https://example.com scrapy shell -L INFO file_path # 使用 -L INFO 是为了避免在ipython里按tab键时候打印很多的 DEBUG 信息。
官方调试文档:
https://docs.scrapy.org/en/latest/topics/debug.html
Parse Command
Scrapy Shell
Open in browser
Logging
5, GUI
可以使用 Scrapy-GUI 来可视化。
6, 工具组合
Selenium
(另外,除了selenium之外,google也出了一款用node.js写的控制浏览器的网页测试库 Puppeteer,可以替代selenium,但是现在它和scrapy结合使用的文档太少。只能用 js 开发, 只支持 Chrome headless。而webdriver 是一个大而全的解决方案,可以用 C#, Java, JS, Python,Ruby开发,支持 IE,FireFox, Safari,Chrome,andriod Chrome, 等等。同样是实现针对 Chrome的测试,webdriver 需要通过调用 chromedriver 提供的 Chrome Remote Debug Protocol, 再封装一层保持接口统一。puppeteer 则是直接和 chrome 建立 websocket 链接,通过 msg, 直接发送 Protocol 让 chrome 来执行对应的操作。如果不考虑兼容性,puppeteer 可以带来更好的性能(少了一层调用的原因),更多的功能,以及 Chrome 团队直接的支持)
headless chrome or headless firefox
BeautifulSoup
scrapy可以很好的抓取静态页面,静态页面可以不需要浏览器参与,但现在动态网站越来越多(即用js来生成页面及元素),因此需要浏览器来完成动态渲染,因此需要无头浏览器的参与。怎么判断页面是动态的还是静态的?
你可以浏览该页面的html源码,在这上面查找你需要的元素,如果不能找到,那么很可能该页面是通过js来动态渲染的。
你还可以通过浏览器来查看:
chrome浏览器如何断点调试异步加载的JS
在我们日常开发中,常常利用chrome强大的控制台Sources下面进行代码断点调试,但是通过$.getScript
等异步加载JS的方式在Sources里面就是找不到,那如何进行debug断点调试呢?下面来一起看看。
这是我们用Sources断点调试的实例图(点击浏览器调试界面的 sources):
当你用Chrome的Network查找异步加载的请求的时候,往往需要在一大堆请求中定位。一个个看过来往往很费时间。一个技巧可以让你快速定位。点击任意一个请求后,按住 Ctrl + F。这时候就会多出一个全局搜索框。然后通过搜索框搜索相关数据就能快速定位到请求了,比如输入 “下一页”等。
认证登陆:
有些页面需要登陆或认证才能访问,最简单的方法就是使用 webdriver,我们可以使用 Selenium 配合webdriver自动填充认证字段。
图形验证码Captcha:
Handling Redirects and Captchas
The Python Requests Library takes care of your HTTP redirects by following them and then returning to the final page. Scrapy also has a powerful way to handle redirects: you can set the redirect middleware to handle redirects.
If you are tired of handling redirect and captchas, you can also use Crawlera in your Scrapy project.
Simple text-based captchas can be solved by using OCR (optical character recognition); you can use pytesseract python library for solving captchas.
Solving captchas is considerable overhead in the scraping process, so if you want to get rid of this overhead, you can employ the help of APIs such as Anti Captcha and Death by Captcha.
Honeypot Traps
Some website developers put honeypot traps in the form of links which are not visible to the typical user on the browser. The easiest way to set the honeypot is by setting the CSS as display: none
. Since the web crawler script does not operate the way a human does, it can try to scrape the information from the link. As a result, the website detects the scraping and blocks the source IP address. 在某些页面加入不浏览器不显示的超级链接,让爬虫去爬,这样就能鉴别是不是爬虫在访问网页,然后禁用调当前ip。
This detection is not easy and requires a significant amount of programming work to accomplish correctly.
7,scrapy + Selenium + headless chrome
先安装:
pip3 install selenium
再安装chrome,然后再下载 chrome webdriver,在这里下载:
Quickstart:
from selenium import webdriver
DRIVER_PATH = '/path/to/chromedriver'
driver = webdriver.Chrome(executable_path=DRIVER_PATH)
driver.get('https://baidu.com')
这会启动一个headful 浏览器,与正常浏览器没有任何区别。
若要启动headless 浏览器,则:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.headless = True
# 与 options.headless = True 作用相同
# chrome_options.add_argument("--headless")
#chrome_options.add_argument("--disable-extensions")
#chrome_options.add_argument("--disable-gpu")
#chrome_options.add_argument("--no-sandbox") # linux only
options.add_argument("--window-size=1920,1200")
driver = webdriver.Chrome(options=options, executable_path=DRIVER_PATH)
driver.get("https://www.nintendo.com/")
print(driver.page_source.encode("utf-8"))
# print(driver.page_source)
driver.quit()
8,增量式爬取
9,分布式爬虫
若运行单机爬虫,只在一台电脑上运行速度是有限的,所以后面我们要想提高抓取效率,需要用到分布式爬虫,在这里需要用到 Redis 来维护一个公共的爬取队列。
分布式爬虫有几个不同模式,会导致结构不一样,举个例子:
1、多台机器同时爬取目标url并且同时从url中抽取数据,N台机器做一模一样的事,通过redis来调度、中转,也就是说它根本没有主机从机之分。
2、总共N+M台机器,其中N台机器负责爬取目标URL,另外M台机器负责容url中抽取数据,N和M做的事不一样,他们也是通过redis来进行调度和中转,它有主机从机之分。
① request之前是放在内存的,现在两台服务器就需要对队列进行集中管理。
② 去重也要进行集中管理
元素等待加载完成与可点击问题:
https://blog.csdn.net/zhusongziye/article/details/80342781
from selenium import webdriver from selenium.common.exceptions import TimeoutException from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from scrapy.http import HtmlResponse from logging import getLogger class SeleniumMiddleware(): def __init__(self, timeout=None, service_args=[]): self.logger = getLogger(__name__) self.timeout = timeout self.browser = webdriver.PhantomJS(service_args=service_args) self.browser.set_window_size(1400, 700) self.browser.set_page_load_timeout(self.timeout) self.wait = WebDriverWait(self.browser, self.timeout) def __del__(self): self.browser.close() def process_request(self, request, spider): """ 用PhantomJS抓取页面 :param request: Request对象 :param spider: Spider对象 :return: HtmlResponse """ self.logger.debug('PhantomJS is Starting') page = request.meta.get('page', 1) try: self.browser.get(request.url) if page > 1: input = self.wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, '#mainsrp-pager div.form > input'))) submit = self.wait.until( EC.element_to_be_clickable((By.CSS_SELECTOR, '#mainsrp-pager div.form > span.btn.J_Submit'))) input.clear() input.send_keys(page) submit.click() self.wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, '#mainsrp-pager li.item.active > span'), str(page))) self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.m-itemlist .items .item'))) return HtmlResponse(url=request.url, body=self.browser.page_source, request=request, encoding='utf-8', status=200) except TimeoutException: return HtmlResponse(url=request.url, status=500, request=request) @classmethod def from_crawler(cls, crawler): return cls(timeout=crawler.settings.get('SELENIUM_TIMEOUT'), service_args=crawler.settings.get('PHANTOMJS_SERVICE_ARGS'))
scrapy 项目对cookie的考虑:
爬取数据时需要考虑免登陆,一般就是用cookie来实现。但是scrapy里用Selenium登陆后获得的cookie不完整,而且有些网站有对Selenium的探测。因此可以考虑先从浏览器手动登陆,然后使用浏览器的cookie来实现免登陆爬取。
打印浏览器的cookie的方法:使用 browsercookie :
import browsercookie browsercookie.chrome()
从正常浏览器获取cookie的好处是可以应对网站对Selenium的检测,通过登陆正常浏览器然后获取cookie,这种方式可以登录淘宝。
但是获取到的cookie是浏览器的所有记录,需要处理一下,并且cookie的字段信息很全。
一条cookie包含的字段有:name、path、value、port、domain、expires等,从正常浏览器获得的cookie可以直接使用在webdriver控制的页面,目前这种方式可以跳过淘宝登录页对Selenium的检测。
还可以将cookie保存到文件,每次抓取的时候先从文件读取。
python selenium webdriver等待某个element的状态发生改变的方法:
使用 selenium.webdriver.support 的 expected_conditions 来实现,Expected Condition 是一个 callable 对象即可,该对象只需要返回bool类型即可。因此有两种方法来实现等待某个element的状态发生改变:
1、定义一个函数:
def not_busy(driver): try: element = driver.find_element_by_id("xxx") except NoSuchElementException: return False return element.get_attribute("aria-busy") == "false" self.wait.until(not_busy)
2、定义一个callable类:
from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.expected_conditions import _find_element from selenium.webdriver.support.ui import WebDriverWait class AttributeTextChanged(object): def __init__(self, locator, attribute, text_before): self.locator = locator self.attribute = attribute self.text_before = text_before def __call__(self, driver): text_after = _find_element(driver, self.locator).get_attribute(self.attribute) return text_after != self.text_before WebDriverWait(browser, 3).until(AttributeTextChanged((By.XPATH, 'xpath'), 'the_attribute_key', old_attribute_value))
其中 def __init__(self, locator, attribute, text_before) 中的attribute指的是某个element的css中的attribute,如 <a href='/uurrll'>go</a> 中的 href 就是。后面调用中的 the_attribute_key 也指的是这个href。
old_attribute_value 指的是 <a href='/uurrll'>go</a> 中 href 被改变之前的值。
为啥会改变,因为有些页面是ajax等异步刷新,当用户点击某个按钮后,只刷新某一个区域,因此某个element的某些属性会改变。那我们再点击按钮后,必须等到浏览器加载完毕才能获取到最新的值。
选取页面元素的方法:
- Tag name
- Class name
- IDs
- XPath
- CSS selectors
scrapy的extension、middleware等类定义里面的 from_crawler 的意义:
就是一个构造器,crawler会先调用这个来构造一个类的实例,可以在这个构造器里获得crawler的参数,也就是整个项目配置的参数,还可以链接一些生命周期信号函数,很有用,如:
import os from six.moves import cPickle as pickle from scrapy import signals from scrapy.utils.job import job_dir class SpiderState(object): """Store and load spider state during a scraping job""" def __init__(self, jobdir=None): self.jobdir = jobdir @classmethod def from_crawler(cls, crawler): obj = cls(job_dir(crawler.settings)) crawler.signals.connect(obj.spider_closed, signal=signals.spider_closed) crawler.signals.connect(obj.spider_opened, signal=signals.spider_opened) return obj def spider_closed(self, spider): if self.jobdir: with open(self.statefn, 'wb') as f: pickle.dump(spider.state, f, protocol=2) def spider_opened(self, spider): if self.jobdir and os.path.exists(self.statefn): with open(self.statefn, 'rb') as f: spider.state = pickle.load(f) else: spider.state = {} @property def statefn(self): return os.path.join(self.jobdir, 'spider.state')
The from_crawler
method (with the @classmethod
decorator) is a factory method that will be used by Scrapy to instantiate the object (spider, extension, middleware, etc) where you added it.
It's often used for getting a reference to the crawler
object (that holds references to objects like settings
, stats
, etc) and then either pass it as arguments to the object being created or set attributes to it.
In the particular example you've pasted, it's being used to read the value from a CRAWLSPIDER_FOLLOW_LINKS
setting and set it to a _follow_links
attribute in the spider.
You can see another simple example of usage of the from_crawler method in this extension that uses the crawler
object to get the value of a setting and passing it as parameter to the extension and to connect some signals to some methods.
python selenium webdriver中element的点击操作:
try: next_page = browser.find_element_by_xpath(self.__xpath_page_next) except NoSuchElementException: return if not next_page.is_displayed(): return next_page.click()
支付宝扫一扫捐赠
微信公众号: 共鸣圈
欢迎讨论,邮件: 924948$qq.com 请把$改成@
QQ群:263132197
QQ: 924948